diff options
Diffstat (limited to 'graphics/pgf/base/tex/generic/graphdrawing')
222 files changed, 40129 insertions, 0 deletions
diff --git a/graphics/pgf/base/tex/generic/graphdrawing/lua/LUA_CODING_STYLE b/graphics/pgf/base/tex/generic/graphdrawing/lua/LUA_CODING_STYLE new file mode 100644 index 0000000000..295a8b2ed1 --- /dev/null +++ b/graphics/pgf/base/tex/generic/graphdrawing/lua/LUA_CODING_STYLE @@ -0,0 +1,27 @@ +Lua coding style for the graphdrawing library +============================================= + +General naming rules and indentation: + + * 2 spaces indentation + * variable_names_with_underscores + * namespaced.low_level_functions_with_underscores(foo, bar) + * CamelCaseClassNames + * CamelCaseClassNames.nameOfStaticFunctionLikeNewOrClone(foo, bar) + * CamelCaseClass:functionNamesLikeThis(foo, bar) + * never use global variables + * Use . for static functions and : for member functions + +LuaDoc comments: + + --- + -- A function to do this and that. + -- + -- @param first Description of the first parameter. + -- @param second Description of the second parameter. + -- + -- @return This function returns something. + -- + function myClass:myFunction(first, second) + ... + end diff --git a/graphics/pgf/base/tex/generic/graphdrawing/lua/pgf.lua b/graphics/pgf/base/tex/generic/graphdrawing/lua/pgf.lua new file mode 100644 index 0000000000..b0a3247616 --- /dev/null +++ b/graphics/pgf/base/tex/generic/graphdrawing/lua/pgf.lua @@ -0,0 +1,72 @@ +-- Copyright 2012 by Till Tantau +-- +-- This file may be distributed an/or modified +-- +-- 1. under the LaTeX Project Public License and/or +-- 2. under the GNU Public License +-- +-- See the file doc/generic/pgf/licenses/LICENSE for more information + +-- @release $Header$ + + +--- +-- The |pgf| namespace lies in the global namespace. It is the only +-- global table defined by \pgfname. The whole graph drawing system, +-- in turn, lies in the table |pgf.gd|. + +pgf = {} + + + +-- Forward +local tostring_table + +--- +-- Writes debug info on the \TeX\ output, separating the parameters +-- by spaces. The debug information will include a complete traceback +-- of the stack, allowing you to see ``where you are'' inside the Lua +-- program. +-- +-- Note that this function resides directly in the |pgf| table. The +-- reason for this is that you can ``always use it'' since |pgf| is +-- always available in the global name space. +-- +-- @param ... List of parameters to write to the \TeX\ output. + +function pgf.debug(...) + local stacktrace = debug.traceback("",2) + texio.write_nl(" ") + texio.write_nl("Debug called for: ") + -- this is to even print out nil arguments in between + local args = {...} + for i = 1, #args do + if i ~= 1 then texio.write(", ") end + texio.write(tostring_table(args[i], "", 5)) + end + texio.write_nl('') + for w in string.gmatch(stacktrace, "/.-:.-:.-%c") do + texio.write('by ', string.match(w,".*/(.*)")) + end +end + + +-- Helper function + +function tostring_table(t, prefix, depth) + if type(t) ~= "table" or (getmetatable(t) and getmetatable(t).__tostring) or depth <= 0 then + return type(t) == "string" and ('"' .. t .. '"') or tostring(t) + else + local r = "{\n" + for k,v in pairs(t) do + r = r .. prefix .. " " .. tostring(k) .. "=" .. + (v==t and "self" or tostring_table(v, prefix .. " ", depth-1)) .. ",\n" + end + return r .. prefix .. "}" + end +end + + + + +return pgf
\ No newline at end of file diff --git a/graphics/pgf/base/tex/generic/graphdrawing/lua/pgf/gd.lua b/graphics/pgf/base/tex/generic/graphdrawing/lua/pgf/gd.lua new file mode 100644 index 0000000000..20d8acafa5 --- /dev/null +++ b/graphics/pgf/base/tex/generic/graphdrawing/lua/pgf/gd.lua @@ -0,0 +1,72 @@ +-- Copyright 2012 by Till Tantau +-- +-- This file may be distributed an/or modified +-- +-- 1. under the LaTeX Project Public License and/or +-- 2. under the GNU Public License +-- +-- See the file doc/generic/pgf/licenses/LICENSE for more information + +--- @release $Header$ + + + + +-- Declare the gd namespace + +local gd = {} +require("pgf").gd = gd + + + + + + + +-- Deprecated: +-- +-- +-- Helping function for creating new algorithm classes +-- +-- This function creates a new algorithm class. This class will have a +-- new method, that takes a graph and, optionally, a parent algorithm +-- as inputs. They will be stored in the "graph" and "parent_algorithm" +-- fields, respectively. +-- +-- @param info This table is used to configure the new class. It has +-- the following fields: First, there is the "properties" table. If +-- this table is present, it will be used as the default table. Second, +-- it can have a graph_parameters table. This table will be used in the +-- constructor to preload graph parameters from the pgf layer. For +-- this, each entry of the table should be of the form +-- +-- key = 'string' +-- +-- What happens is that upon the creation of a new algorithm object, +-- for each key we lookup the graph option 'string' and +-- store its value in the key of the new algorithm object. +-- +-- @return A table that is a class with a new function setup. + +function gd.new_algorithm_class (class) + class.__index = class + class.new = + function (initial) + + -- Create new object + local obj = {} + for k,v in pairs(initial) do + obj[k] = v + end + setmetatable(obj, class) + + return obj + end + class.preconditions = {} + class.postconditions = {} + + return class +end + + +return gd diff --git a/graphics/pgf/base/tex/generic/graphdrawing/lua/pgf/gd/bindings.lua b/graphics/pgf/base/tex/generic/graphdrawing/lua/pgf/gd/bindings.lua new file mode 100644 index 0000000000..410da3af45 --- /dev/null +++ b/graphics/pgf/base/tex/generic/graphdrawing/lua/pgf/gd/bindings.lua @@ -0,0 +1,26 @@ +-- Copyright 2012 by Till Tantau +-- +-- This file may be distributed an/or modified +-- +-- 1. under the LaTeX Project Public License and/or +-- 2. under the GNU Public License +-- +-- See the file doc/generic/pgf/licenses/LICENSE for more information + +--- @release $Header$ + + + +-- Imports + +require "pgf" +require "pgf.gd" + + +-- Declare namespace +pgf.gd.bindings = {} + + +-- Done + +return pgf.gd.bindings
\ No newline at end of file diff --git a/graphics/pgf/base/tex/generic/graphdrawing/lua/pgf/gd/bindings/Binding.lua b/graphics/pgf/base/tex/generic/graphdrawing/lua/pgf/gd/bindings/Binding.lua new file mode 100644 index 0000000000..6ffb4168f5 --- /dev/null +++ b/graphics/pgf/base/tex/generic/graphdrawing/lua/pgf/gd/bindings/Binding.lua @@ -0,0 +1,265 @@ +-- Copyright 2012 by Till Tantau +-- +-- This file may be distributed an/or modified +-- +-- 1. under the LaTeX Project Public License and/or +-- 2. under the GNU Public License +-- +-- See the file doc/generic/pgf/licenses/LICENSE for more information + +-- @release $Header$ + + +-- Imports +local Storage = require "pgf.gd.lib.Storage" + + +--- +-- This class provides a (non-functional) default implementation of a +-- binding between a display layer and the algorithm layer. Subclasses +-- must implement most of the member functions declared in this class. +-- +-- A instance of a subclass of this class encapsulates the complete +-- implementation of all specific code needed for the communication +-- between the display layer and the graph drawing engine. +-- +-- Note that you never call the methods of a |Binding| object +-- directly, neither from the display layer nor from the algorithm +-- layer. Instead, you use the more powerful and more easy to use +-- functions from |InterfaceToDisplay| and +-- |InterfaceToAlgorithms|. They call the appropriate |Binding| +-- methods internally. +-- +-- Because of the above, in order to use the graph drawing system +-- inside a new display layer, you need to subclass |Binding| and +-- implement all the functions. Then you need to write the display +-- layer in such a way that it calls the appropriate functions from +-- |InterfaceToDisplay|. +-- +-- @field storage A |Storage| storing the information passed from the +-- display layer. The interpretation of this left to the actual +-- binding. + +local Binding = { + storage = Storage.newTableStorage () +} +Binding.__index = Binding + +-- Namespace +require("pgf.gd.bindings").Binding = Binding + + + + + +-- +-- This method gets called whenever the graph drawing coroutine should +-- be resumed. First, the binding layer should ask the display layer +-- to execute the |code|, then, after this is done, the function +-- |InterfaceToDisplay.resumeGraphDrawingCoroutine| should be called +-- by this function. +-- +-- @param code Some code to be executed by the display layer. + +function Binding:resumeGraphDrawingCoroutine(code) + -- Does nothing by default +end + + +--- +-- 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|. +-- +-- @param t See |InterfaceToAlgorithms.declare| for details. + +function Binding:declareCallback(t) + -- Does nothing by default +end + + + + +-- Rendering + +--- +-- 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:renderStart() + -- Does nothing by default +end + +--- +-- See |renderStart|. + +function Binding:renderStop() + -- Does nothing by default +end + + + + + +--- +-- This function and the corresponding |...Stop...| functions are +-- called whenever a collection kind should be rendered. See +-- |InterfaceToDisplay.render_collections| for details. +-- +-- @param kind The kind (a string). +-- @param layer The kind's layer (a number). + +function Binding:renderCollectionStartKind(kind, layer) + -- Does nothing by default +end + + +--- +-- The counterpart to |renderCollectionStartKind|. +-- +-- @param kind The kind. +-- @param layer The kind's layer. + +function Binding:renderCollectionStopKind(kind, layer) + -- Does nothing by default +end + + +--- +-- Renders a single collection, see |renderCollectionStartKind| for +-- details. +-- +-- @param collection The collection object. + +function Binding:renderCollection(collection) + -- Does nothing by default +end + + + +--- +-- This function and the corresponding |...Stop...| functions are +-- called whenever a vertex should be rendered. See +-- |InterfaceToDisplay.render_vertices| for details. +-- + +function Binding:renderVerticesStart() + -- Does nothing by default +end + + +--- +-- The counterpart to |renderVerticesStop|. +-- + +function Binding:renderVerticesStop() + -- Does nothing by default +end + + +--- +-- Renders a single vertex, see |renderVertexStartKind| for +-- details. +-- +-- @param vertex The |Vertex| object. + +function Binding:renderVertex(vertex) + -- Does nothing by default +end + + + +--- +-- 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|. +-- +-- @param v The vertex. + +function Binding:everyVertexCreation(v) + -- Does nothing by default +end + + + + + +--- +-- This function and the corresponding |...Stop...| functions are +-- called whenever an edge should be rendered. See +-- |InterfaceToDisplay.render_edges| for details. +-- + +function Binding:renderEdgesStart() + -- Does nothing by default +end + + +--- +-- The counterpart to |renderEdgesStop|. +-- + +function Binding:renderEdgesStop() + -- Does nothing by default +end + + +--- +-- Renders a single vertex, see |renderEdgeStartKind| for +-- details. +-- +-- @param edge The |Edge| object. + +function Binding:renderEdge(edge) + -- Does nothing by default +end + + +--- +-- Like |everyVertexCreation|, only for edges. +-- +-- @param e The edge. + +function Binding:everyEdgeCreation(e) + -- Does nothing by default +end + + +--- +-- Generate a new vertex. This method will be called when the +-- \emph{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. +-- +-- @param init A table of initial values for the node. The following +-- fields will be used: +-- % +-- \begin{itemize} +-- \item |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. +-- \item |shape| If present, a shape of the node. +-- \item |generated_options| A table that is passed back to the +-- display layer as a list of key--value pairs. +-- \item |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. +-- \end{itemize} + +function Binding:createVertex(init) + -- Does nothing by default +end + + + + +return Binding
\ No newline at end of file diff --git a/graphics/pgf/base/tex/generic/graphdrawing/lua/pgf/gd/bindings/BindingToPGF.lua b/graphics/pgf/base/tex/generic/graphdrawing/lua/pgf/gd/bindings/BindingToPGF.lua new file mode 100644 index 0000000000..5324a1652e --- /dev/null +++ b/graphics/pgf/base/tex/generic/graphdrawing/lua/pgf/gd/bindings/BindingToPGF.lua @@ -0,0 +1,374 @@ +-- Copyright 2012 by Till Tantau +-- +-- This file may be distributed an/or modified +-- +-- 1. under the LaTeX Project Public License and/or +-- 2. under the GNU Public License +-- +-- See the file doc/generic/pgf/licenses/LICENSE for more information + +-- @release $Header$ + + + +-- Imports +local Storage = require "pgf.gd.lib.Storage" + + +--- +-- This class, which is a subclass of |Binding|, binds the graph +-- drawing system to the \pgfname\ display system by overriding (that +-- is, implementing) the methods of the |Binding| class. As a typical +-- example, consider the implementation of the function |renderVertex|: +-- % +--\begin{codeexample}[code only, tikz syntax=false] +--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 +--\end{codeexample} +-- +-- As can be seen, the main job of this function is to call a function +-- on the \TeX\ 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 +-- \TeX\ layer, all of which are implemented in the \pgfname\ 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). + +local BindingToPGF = { + storage = Storage.newTableStorage () -- overwrite default storage +} +BindingToPGF.__index = BindingToPGF +setmetatable(BindingToPGF, require "pgf.gd.bindings.Binding") -- subclass of Binding + + +-- Namespace +require("pgf.gd.bindings").BindingToPGF = BindingToPGF + +-- Imports +local lib = require "pgf.gd.lib" + +local Coordinate = require "pgf.gd.model.Coordinate" +local Path = require "pgf.gd.model.Path" + +-- The implementation + +-- Forward +local table_in_pgf_syntax +local animations_in_pgf_syntax +local path_in_pgf_syntax +local coordinate_in_pgf_syntax + + + + +-- Scope handling + +function BindingToPGF:resumeGraphDrawingCoroutine(text) + tex.print(text) + tex.print("\\pgfgdresumecoroutinetrue") +end + + +-- Declarations + +function BindingToPGF:declareCallback(t) + tex.print("\\pgfgdcallbackdeclareparameter{" .. t.key .. "}{" .. (t.type or "nil") .. "}") +end + + + +-- Rendering + +function BindingToPGF:renderStart() + tex.print("\\pgfgdcallbackbeginshipout") +end + +function BindingToPGF:renderStop() + tex.print("\\pgfgdcallbackendshipout") +end + + +-- Rendering collections + +function BindingToPGF:renderCollection(collection) + tex.print("\\pgfgdcallbackrendercollection{".. collection.kind .. "}{" + .. table_in_pgf_syntax(collection.generated_options) .. "}") +end + +function BindingToPGF:renderCollectionStartKind(kind, layer) + tex.print("\\pgfgdcallbackrendercollectionkindstart{" .. kind .. "}{" .. tostring(layer) .. "}") +end + +function BindingToPGF:renderCollectionStopKind(kind, layer) + tex.print("\\pgfgdcallbackrendercollectionkindstop{" .. kind .. "}{" .. tostring(layer) .. "}") +end + +-- Printing points + +local function to_pt(x) + return string.format("%.12fpt", x) +end + + +-- Managing vertices (pgf nodes) + +local boxes = {} +local box_count = 0 + +function BindingToPGF:everyVertexCreation(v) + local info = self.storage[v] + + -- Save the box! + box_count = box_count + 1 + boxes[box_count] = node.copy_list(tex.box[info.tex_box_number]) + + -- Special tex stuff, should not be considered by gd algorithm + info.box_count = box_count +end + +function BindingToPGF:renderVertex(v) + local info = assert(self.storage[v], "thou shalt not modify the syntactic digraph") + tex.print( + string.format( + "\\pgfgdcallbackrendernode{%s}{%.12fpt}{%.12fpt}{%.12fpt}{%.12fpt}{%.12fpt}{%.12fpt}{%s}{%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, + animations_in_pgf_syntax(v.animations))) +end + +function BindingToPGF:retrieveBox(index, box_num) + tex.box[box_num] = assert(boxes[index], "no box stored at given index") + boxes[index] = nil -- remove from memory +end + +function BindingToPGF:renderVerticesStart() + tex.print("\\pgfgdcallbackbeginnodeshipout") +end + +function BindingToPGF:renderVerticesStop() + tex.print("\\pgfgdcallbackendnodeshipout") +end + + +local function rigid(x) + if type(x) == "function" then + return x() + else + return x + end +end + + +-- Managing edges + +function BindingToPGF:renderEdge(e) + local info = assert(self.storage[e], "thou shalt not modify the syntactic digraph") + + local function get_anchor(e, anchor) + local a = e.options[anchor] + if a and a ~= "" then + return "." .. a + else + return "" + end + end + + local callback = { + '\\pgfgdcallbackedge', + '{', e.tail.name .. get_anchor(e, "tail anchor"), '}', + '{', e.head.name .. get_anchor(e, "head anchor"), '}', + '{', e.direction, '}', + '{', info.pgf_options or "", '}', + '{', info.pgf_edge_nodes or "", '}', + '{', table_in_pgf_syntax(e.generated_options), '}', + '{' + } + + local i = 1 + while i <= #e.path do + local c = e.path[i] + assert (type(c) == "string", "illegal path operand") + + if c == "lineto" then + i = i + 1 + local d = rigid(e.path[i]) + callback [#callback + 1] = '--(' .. to_pt(d.x) .. ',' .. to_pt(d.y) .. ')' + i = i + 1 + elseif c == "moveto" then + i = i + 1 + local d = rigid(e.path[i]) + callback [#callback + 1] = '(' .. to_pt(d.x) .. ',' .. to_pt(d.y) .. ')' + i = i + 1 + elseif c == "closepath" then + callback [#callback + 1] = '--cycle' + i = i + 1 + elseif c == "curveto" then + local d1, d2, d3 = rigid(e.path[i+1]), rigid(e.path[i+2]), rigid(e.path[i+3]) + i = i + 3 + callback [#callback + 1] = '..controls(' .. to_pt(d1.x) .. ',' .. to_pt(d1.y) .. ')and(' + .. to_pt(d2.x) .. ',' .. to_pt(d2.y) .. ')..' + callback [#callback + 1] = '(' .. to_pt(d3.x) .. ',' .. to_pt(d3.y) .. ')' + i = i + 1 + else + error("illegal operation in edge path") + end + end + + callback [#callback + 1] = '}' + callback [#callback + 1] = '{' .. animations_in_pgf_syntax(e.animations) .. '}' + + -- hand TikZ code over to TeX + tex.print(table.concat(callback)) +end + + +function BindingToPGF:renderEdgesStart() + tex.print("\\pgfgdcallbackbeginedgeshipout") +end + +function BindingToPGF:renderEdgesStop() + tex.print("\\pgfgdcallbackendedgeshipout") +end + + +-- Vertex creation + +function BindingToPGF:createVertex(init) + -- Now, go back to TeX... + coroutine.yield( + table.concat({ + "\\pgfgdcallbackcreatevertex{", init.name, "}", + "{", init.shape, "}", + "{", table_in_pgf_syntax(init.generated_options), ",", init.pgf_options or "", "}", + "{", (init.text or ""), "}" + })) + -- ... and come back with a new node! +end + + + +-- Local helpers + +function table_in_pgf_syntax (t) + local prefix = "/graph drawing/" + local suffix = "/.try" + return table.concat( lib.imap( t, function(table) + if table.value then + return prefix .. table.key .. suffix .. "={" .. tostring(table.value) .. "}" + else + return prefix .. table.key .. suffix + end + end), ",") +end + + +function animations_in_pgf_syntax (a) + return + table.concat( + lib.imap( + a, + function(animation) + return "\\pgfanimateattribute{" .. animation.attribute .. "}{whom=pgf@gd," .. + table.concat( + lib.imap ( + animation.entries, + function (entry) + return "entry={" .. entry.t .. "s}{" .. to_pgf(entry.value) .. "}" + end + ), ",") .. + "," .. + table.concat( + lib.imap( + animation.options or {}, + function(table) + if table.value then + return table.key .. "={" .. to_pgf(table.value) .. "}" + else + return table.key + end + end), ",") + .. "}" + end) + ) +end + + +function to_pgf(x) + if type (x) == "table" then + if getmetatable(x) == Coordinate then + return coordinate_in_pgf_syntax(x) + elseif getmetatable(x) == Path then + return path_in_pgf_syntax(x) + else + error("illegal table in value of a key to be passed back to pgf") + end + else + return tostring(x) + end +end + +function path_in_pgf_syntax (p) + + local s = {} + + local i = 1 + while i <= #p do + local c = p[i] + assert (type(c) == "string", "illegal path operand") + + if c == "lineto" then + i = i + 1 + local d = rigid(p[i]) + s [#s + 1] = '\\pgfpathlineto{\\pgfqpoint{' .. to_pt(d.x) .. '}{' .. to_pt(d.y) .. '}}' + i = i + 1 + elseif c == "moveto" then + i = i + 1 + local d = rigid(p[i]) + s [#s + 1] = '\\pgfpathmoveto{\\pgfqpoint{' .. to_pt(d.x) .. '}{' .. to_pt(d.y) .. '}}' + i = i + 1 + elseif c == "closepath" then + s [#s + 1] = '\\pgfpathclose' + i = i + 1 + elseif c == "curveto" then + local d1, d2, d3 = rigid(p[i+1]), rigid(p[i+2]), rigid(p[i+3]) + i = i + 3 + s [#s + 1] = '\\pgfpathcurveto{\\pgfqpoint{' .. to_pt(d1.x) .. '}{' .. to_pt(d1.y) .. '}}{\\pgfqpoint{' + .. to_pt(d2.x) .. '}{' .. to_pt(d2.y) .. '}}{\\pgfqpoint{' + .. to_pt(d3.x) .. '}{' .. to_pt(d3.y) .. '}}' + i = i + 1 + else + error("illegal operation in edge path") + end + end + + return table.concat(s) +end + +function coordinate_in_pgf_syntax(c) + return '\\pgfqpoint{'..to_pt(c.x) .. '}{'.. to_pt(c.y) .. '}' +end + + +return BindingToPGF diff --git a/graphics/pgf/base/tex/generic/graphdrawing/lua/pgf/gd/circular.lua b/graphics/pgf/base/tex/generic/graphdrawing/lua/pgf/gd/circular.lua new file mode 100644 index 0000000000..cddfd96dfd --- /dev/null +++ b/graphics/pgf/base/tex/generic/graphdrawing/lua/pgf/gd/circular.lua @@ -0,0 +1,22 @@ +-- Copyright 2012 by Till Tantau +-- +-- This file may be distributed an/or modified +-- +-- 1. under the LaTeX Project Public License and/or +-- 2. under the GNU Public License +-- +-- See the file doc/generic/pgf/licenses/LICENSE for more information + +--- @release $Header$ + + + +local circular = {} + +-- Declare namespace +require("pgf.gd").circular = circular + + +-- Done + +return circular
\ No newline at end of file diff --git a/graphics/pgf/base/tex/generic/graphdrawing/lua/pgf/gd/circular/Tantau2012.lua b/graphics/pgf/base/tex/generic/graphdrawing/lua/pgf/gd/circular/Tantau2012.lua new file mode 100644 index 0000000000..bebb87ab3e --- /dev/null +++ b/graphics/pgf/base/tex/generic/graphdrawing/lua/pgf/gd/circular/Tantau2012.lua @@ -0,0 +1,153 @@ +-- Copyright 2012 by Till Tantau +-- +-- This file may be distributed an/or modified +-- +-- 1. under the LaTeX Project Public License and/or +-- 2. under the GNU Public License +-- +-- See the file doc/generic/pgf/licenses/LICENSE for more information + +-- @release $Header$ + + +-- Imports +local declare = require("pgf.gd.interface.InterfaceToAlgorithms").declare + +local routing = require("pgf.gd.routing") + +-- The algorithm class +local Tantau2012 = {} + +--- +declare { + key = "simple necklace layout", + algorithm = Tantau2012, + + postconditions = { + upward_oriented = true + }, + + documentation_in = "pgf.gd.circular.doc" +} + + + +-- Imports + +local Coordinate = require "pgf.gd.model.Coordinate" +local Hints = require "pgf.gd.routing.Hints" + +local lib = require "pgf.gd.lib" + + + + +-- The implementation + +function Tantau2012:run() + local g = self.ugraph + local vertices = g.vertices + local n = #vertices + + local sib_dists = self:computeNodeDistances () + local radii = self:computeNodeRadii() + local diam, adjusted_radii = self:adjustNodeRadii(sib_dists, radii) + + -- Compute total necessary length. For this, iterate over all + -- consecutive pairs and keep track of the necessary space for + -- this node. We imagine the nodes to be aligned from left to + -- right in a line. + local carry = 0 + local positions = {} + local function wrap(i) return (i-1)%n + 1 end + local ideal_pos = 0 + for i = 1,n do + positions[i] = ideal_pos + carry + ideal_pos = ideal_pos + sib_dists[i] + local node_sep = + lib.lookup_option('node post sep', vertices[i], g) + + lib.lookup_option('node pre sep', vertices[wrap(i+1)], g) + local arc = node_sep + adjusted_radii[i] + adjusted_radii[wrap(i+1)] + local needed = carry + arc + local dist = math.sin( arc/diam ) * diam + needed = needed + math.max ((radii[i] + radii[wrap(i+1)]+node_sep)-dist, 0) + carry = math.max(needed-sib_dists[i],0) + end + local length = ideal_pos + carry + + local radius = length / (2 * math.pi) + for i,vertex in ipairs(vertices) do + vertex.pos.x = radius * math.cos(2 * math.pi * (positions[i] / length + 1/4)) + vertex.pos.y = -radius * math.sin(2 * math.pi * (positions[i] / length + 1/4)) + end + + -- Add routing infos + local necklace = lib.icopy({g.vertices[1]}, lib.icopy(g.vertices)) + Hints.addNecklaceCircleHint(g, necklace, nil, true) +end + + +function Tantau2012:computeNodeDistances() + local sib_dists = {} + local sum_length = 0 + local vertices = self.digraph.vertices + for i=1,#vertices do + sib_dists[i] = lib.lookup_option('node distance', vertices[i], self.digraph) + sum_length = sum_length + sib_dists[i] + end + + local missing_length = self.digraph.options['radius'] * 2 * math.pi - sum_length + if missing_length > 0 then + -- Ok, the sib_dists to not add up to the desired minimum value. + -- What should we do? Hmm... We increase all by the missing amount: + for i=1,#vertices do + sib_dists[i] = sib_dists[i] + missing_length/#vertices + end + end + + sib_dists.total = math.max(self.digraph.options['radius'] * 2 * math.pi, sum_length) + + return sib_dists +end + + +function Tantau2012:computeNodeRadii() + local radii = {} + for i,v in ipairs(self.digraph.vertices) do + local min_x, min_y, max_x, max_y = v:boundingBox() + local w, h = max_x-min_x, max_y-min_y + if v.shape == "circle" or v.shape == "ellipse" then + radii[i] = math.max(w,h)/2 + else + radii[i] = math.sqrt(w*w + h*h)/2 + end + end + return radii +end + + +function Tantau2012:adjustNodeRadii(sib_dists,radii) + local total = 0 + local max_rad = 0 + for i=1,#radii do + total = total + 2*radii[i] + + lib.lookup_option('node post sep', self.digraph.vertices[i], self.digraph) + + lib.lookup_option('node pre sep', self.digraph.vertices[i], self.digraph) + max_rad = math.max(max_rad, radii[i]) + end + total = math.max(total, sib_dists.total, max_rad*math.pi) + local diam = total/(math.pi) + + -- Now, adjust the radii: + local adjusted_radii = {} + for i=1,#radii do + adjusted_radii[i] = (math.pi - 2*math.acos(radii[i]/diam))*diam/2 + end + + return diam, adjusted_radii +end + + +-- done + +return Tantau2012 diff --git a/graphics/pgf/base/tex/generic/graphdrawing/lua/pgf/gd/circular/doc.lua b/graphics/pgf/base/tex/generic/graphdrawing/lua/pgf/gd/circular/doc.lua new file mode 100644 index 0000000000..9198885965 --- /dev/null +++ b/graphics/pgf/base/tex/generic/graphdrawing/lua/pgf/gd/circular/doc.lua @@ -0,0 +1,145 @@ +-- Copyright 2013 by Till Tantau +-- +-- This file may be distributed an/or modified +-- +-- 1. under the LaTeX Project Public License and/or +-- 2. under the GNU Public License +-- +-- See the file doc/generic/pgf/licenses/LICENSE for more information + +-- @release $Header$ + +local key = require 'pgf.gd.doc'.key +local documentation = require 'pgf.gd.doc'.documentation +local summary = require 'pgf.gd.doc'.summary +local example = require 'pgf.gd.doc'.example + +-------------------------------------------------------------------- +key "simple necklace layout" + +summary +[[ +This simple layout arranges the nodes in a circle, which is +especially useful for drawing, well, circles of nodes. +]] + +documentation +[[ +The name |simple necklace layout| is reminiscent of the more general +``necklace layout'', a term coined by Speckmann and Verbeek in +their paper +% +\begin{itemize} + \item + Bettina Speckmann and Kevin Verbeek, + \newblock Necklace Maps, + \newblock \emph{IEEE Transactions on Visualization and Computer + Graphics,} 16(6):881--889, 2010. +\end{itemize} + +For a |simple necklace layout|, the centers of the nodes +are placed on a counter-clockwise circle, starting with the first +node at the |grow| direction (for |grow'|, the circle is +clockwise). The order of the nodes is the order in which they appear +in the graph, the edges are not taken into consideration, unless the +|componentwise| option is given. +% +\begin{codeexample}[ + preamble={\usetikzlibrary{arrows.meta,graphs,graphdrawing} + \usegdlibrary{circular}}] +\tikz[>={Stealth[round,sep]}] + \graph [simple necklace layout, grow'=down, node sep=1em, + nodes={draw,circle}, math nodes] + { + x_1 -> x_2 -> x_3 -> x_4 -> + x_5 -> "\dots"[draw=none] -> "x_{n-1}" -> x_n -> x_1 + }; +\end{codeexample} + +When you give the |componentwise| option, the graph will be +decomposed into connected components, which are then laid out +individually and packed using the usual component packing +mechanisms: +% +\begin{codeexample}[preamble={\usetikzlibrary{graphs,graphdrawing} + \usegdlibrary{circular}}] +\tikz \graph [simple necklace layout] { + a -- b -- c -- d -- a, + 1 -- 2 -- 3 -- 1 +}; +\end{codeexample} +% +\begin{codeexample}[preamble={\usetikzlibrary{graphs,graphdrawing} + \usegdlibrary{circular}}] +\tikz \graph [simple necklace layout, componentwise] { + a -- b -- c -- d -- a, + 1 -- 2 -- 3 -- 1 +}; +\end{codeexample} + +The nodes are placed in such a way that +% +\begin{enumerate} + \item The (angular) distance between the centers of consecutive + nodes is at least |node distance|, + \item the distance between the borders of consecutive nodes is at + least |node sep|, and + \item the radius is at least |radius|. +\end{enumerate} +% +The radius of the circle is chosen near-minimal such that the above +properties are satisfied. To be more precise, if all nodes are +circles, the radius is chosen optimally while for, say, rectangular +nodes there may be too much space between the nodes in order to +satisfy the second condition. +]] + +example +[[ +\tikz \graph [simple necklace layout, + node sep=0pt, node distance=0pt, + nodes={draw,circle}] +{ 1 -- 2 [minimum size=30pt] -- 3 -- + 4 [minimum size=50pt] -- 5 [minimum size=40pt] -- 6 -- 7 }; +]] + +example +[[ +\begin{tikzpicture}[radius=1.25cm] + \graph [simple necklace layout, + node sep=0pt, node distance=0pt, + nodes={draw,circle}] + { 1 -- 2 [minimum size=30pt] -- 3 -- + 4 [minimum size=50pt] -- 5 [minimum size=40pt] -- 6 -- 7 }; + + \draw [red] (0,-1.25) circle []; +\end{tikzpicture} +]] + +example +[[ +\tikz \graph [simple necklace layout, + node sep=0pt, node distance=1cm, + nodes={draw,circle}] +{ 1 -- 2 [minimum size=30pt] -- 3 -- + 4 [minimum size=50pt] -- 5 [minimum size=40pt] -- 6 -- 7 }; +]] + +example +[[ +\tikz \graph [simple necklace layout, + node sep=2pt, node distance=0pt, + nodes={draw,circle}] +{ 1 -- 2 [minimum size=30pt] -- 3 -- + 4 [minimum size=50pt] -- 5 [minimum size=40pt] -- 6 -- 7 }; +]] + +example +[[ +\tikz \graph [simple necklace layout, + node sep=0pt, node distance=0pt, + nodes={rectangle,draw}] +{ 1 -- 2 [minimum size=30pt] -- 3 -- + 4 [minimum size=50pt] -- 5 [minimum size=40pt] -- 6 -- 7 }; +]] +-------------------------------------------------------------------- diff --git a/graphics/pgf/base/tex/generic/graphdrawing/lua/pgf/gd/circular/library.lua b/graphics/pgf/base/tex/generic/graphdrawing/lua/pgf/gd/circular/library.lua new file mode 100644 index 0000000000..016cfe483f --- /dev/null +++ b/graphics/pgf/base/tex/generic/graphdrawing/lua/pgf/gd/circular/library.lua @@ -0,0 +1,30 @@ +-- Copyright 2012 by Till Tantau +-- +-- This file may be distributed an/or modified +-- +-- 1. under the LaTeX Project Public License and/or +-- 2. under the GNU Public License +-- +-- See the file doc/generic/pgf/licenses/LICENSE for more information + +-- @release $Header$ + + +-- Imports +local declare = require "pgf.gd.interface.InterfaceToAlgorithms".declare + +--- +-- ``Circular'' graph drawing algorithms arrange the nodes of a graph +-- on one of more circles. +-- +-- @library + +local circular -- Library name + +-- Load declarations from: + +-- Load algorithms from: +require "pgf.gd.circular.Tantau2012" + + +-- General declarations diff --git a/graphics/pgf/base/tex/generic/graphdrawing/lua/pgf/gd/control.lua b/graphics/pgf/base/tex/generic/graphdrawing/lua/pgf/gd/control.lua new file mode 100644 index 0000000000..a3bec1fef3 --- /dev/null +++ b/graphics/pgf/base/tex/generic/graphdrawing/lua/pgf/gd/control.lua @@ -0,0 +1,26 @@ +-- Copyright 2012 by Till Tantau +-- +-- This file may be distributed an/or modified +-- +-- 1. under the LaTeX Project Public License and/or +-- 2. under the GNU Public License +-- +-- See the file doc/generic/pgf/licenses/LICENSE for more information + +-- @release $Header$ + + + +-- Imports + +require "pgf" +require "pgf.gd" + + +-- Declare namespace +pgf.gd.control = {} + + +-- Done + +return pgf.gd.control
\ No newline at end of file diff --git a/graphics/pgf/base/tex/generic/graphdrawing/lua/pgf/gd/control/Anchoring.lua b/graphics/pgf/base/tex/generic/graphdrawing/lua/pgf/gd/control/Anchoring.lua new file mode 100644 index 0000000000..03a05ee627 --- /dev/null +++ b/graphics/pgf/base/tex/generic/graphdrawing/lua/pgf/gd/control/Anchoring.lua @@ -0,0 +1,104 @@ +-- Copyright 2012 by Till Tantau +-- +-- This file may be distributed an/or modified +-- +-- 1. under the LaTeX Project Public License and/or +-- 2. under the GNU Public License +-- +-- See the file doc/generic/pgf/licenses/LICENSE for more information + +-- @release $Header$ + + + +--- +-- @section subsection {Anchoring a Graph} +-- +-- \label{subsection-library-graphdrawing-anchoring} +-- +-- A graph drawing algorithm must compute positions of the nodes of a +-- graph, but the computed positions are only \emph{relative} (``this +-- node is left of this node, but above that other node''). It is not +-- immediately obvious where the ``the whole graph'' should be placed +-- \emph{absolutely} once all relative positions have been computed. In +-- case that the graph consists of several unconnected components, the +-- situation is even more complicated. +-- +-- The order in which the algorithm layer determines the node at which +-- the graph should be anchored: +-- % +-- \begin{enumerate} +-- \item If the |anchor node=|\meta{node name} option given to the graph +-- as a whole, the graph is anchored at \meta{node name}, provided +-- there is a node of this name in the graph. (If there is no node of +-- this name or if it is misspelled, the effect is the same as if this +-- option had not been given at all.) +-- \item Otherwise, if there is a node where the |anchor here| option is +-- specified, the first node with this option set is used. +-- \item Otherwise, if there is a node where the |desired at| option is +-- set (perhaps implicitly through keys like |x|), the first such node +-- is used. +-- \item Finally, in all other cases, the first node is used. +-- \end{enumerate} +-- +-- In the above description, the ``first'' node refers to the node first +-- encountered in the specification of the graph. +-- +-- Once the node has been determined, the graph is shifted so that +-- this node lies at the position specified by |anchor at|. +-- +-- @end + + + +local Anchoring = {} + + +-- Namespace +require("pgf.gd.control").Anchoring = Anchoring + + +-- Imports +local Coordinate = require("pgf.gd.model.Coordinate") +local declare = require "pgf.gd.interface.InterfaceToAlgorithms".declare + + + +--- +declare { + key = "desired at", + type = "coordinate", + documentation_in = "pgf.gd.control.doc" +} + +--- +declare { + key = "anchor node", + type = "string", + documentation_in = "pgf.gd.control.doc" +} + + +--- +declare { + key = "anchor at", + type = "canvas coordinate", + initial = "(0pt,0pt)", + documentation_in = "pgf.gd.control.doc" +} + + +--- +declare { + key = "anchor here", + type = "boolean", + documentation_in = "pgf.gd.control.doc" +} + + + + + +-- Done + +return Anchoring
\ No newline at end of file diff --git a/graphics/pgf/base/tex/generic/graphdrawing/lua/pgf/gd/control/ComponentAlign.lua b/graphics/pgf/base/tex/generic/graphdrawing/lua/pgf/gd/control/ComponentAlign.lua new file mode 100644 index 0000000000..72a82be978 --- /dev/null +++ b/graphics/pgf/base/tex/generic/graphdrawing/lua/pgf/gd/control/ComponentAlign.lua @@ -0,0 +1,540 @@ +-- Copyright 2012 by Till Tantau +-- +-- This file may be distributed an/or modified +-- +-- 1. under the LaTeX Project Public License and/or +-- 2. under the GNU Public License +-- +-- See the file doc/generic/pgf/licenses/LICENSE for more information + +-- @release $Header$ + + +local declare = require "pgf.gd.interface.InterfaceToAlgorithms".declare + + +--- +-- @section subsubsection {Aligning Components} +-- +-- When components are placed next to each from left to right, it +-- is not immediately clear how the components should be aligned +-- vertically. What happens is that in each component a horizontal line is +-- determined and then all components are shifted vertically so that the +-- lines are aligned. There are different strategies for choosing these +-- ``lines'', see the description of the options described later on. +-- When the |component direction| option is used to change the direction +-- in which components are placed, it certainly make no longer sense to +-- talk about ``horizontal'' and ``vertical'' lines. Instead, what +-- actually happens is that the alignment does not consider +-- ``horizontal'' lines, but lines that go in the direction specified by +-- |component direction| and aligns them by moving components along a +-- line that is perpendicular to the line. For these reasons, let us call +-- the line in the component direction the \emph{alignment line} and a +-- line that is perpendicular to it the \emph{shift line}. +-- +-- The first way of specifying through which point of a component the +-- alignment line should get is to use the option |align here|. +-- In many cases, however, you will not wish to specify an alignment node +-- manually in each component. Instead, you will use the +-- |component align| key to specify a \emph{strategy} that should be used to +-- automatically determine such a node. +-- +-- Using a combination of |component direction| and |component align|, +-- numerous different packing strategies can be configured. However, +-- since names like |counterclockwise| are a bit hard to remember and to +-- apply in practice, a number of easier-to-remember keys are predefined +-- that combine an alignment and a direction. +-- +-- @end + +--- + +declare { + key = "align here", + type = "boolean", + + summary = [[" + When this option is given to a node, this alignment line will go + through the origin of this node. If this option is passed to more + than one node of a component, the node encountered first in the + component is used. + "]], + examples = [[" + \tikz \graph [binary tree layout, nodes={draw}] + { a, b -- c[align here], d -- e[second, align here] -- f }; + "]] +} + +--- + +declare { + key = "component align", + type = "string", + initial = "first node", + + summary = [[" + Specifies a ``strategy'' for the alignment of components. + "]], + documentation = [[" + The following values are permissible: + % + \begin{itemize} + \item \declare{|first node|} + + In each component, the alignment line goes through the center of + the first node of the component encountered during specification + of the component. + % +\begin{codeexample}[preamble={\usetikzlibrary{graphs,graphdrawing} + \usegdlibrary{trees}}] +\tikz \graph [binary tree layout, nodes={draw}, + component align=first node] + { a, b -- c, d -- e[second] -- f }; +\end{codeexample} + % + \item \declare{|center|} + + The nodes of the component are projected onto the shift line. The + alignment line is now chosen so that it is exactly in the middle + between the maximum and minimum value that the projected nodes + have on the shift line. + % +\begin{codeexample}[preamble={\usetikzlibrary{graphs,graphdrawing} + \usegdlibrary{trees}}] +\tikz \graph [binary tree layout, nodes={draw}, + component align=center] + { a, b -- c, d -- e[second] -- f }; +\end{codeexample} + % +\begin{codeexample}[preamble={\usetikzlibrary{graphs,graphdrawing} + \usegdlibrary{trees}}] +\tikz \graph [binary tree layout, nodes={draw}, + component direction=90, + component align=center] + { a, b -- c, d -- e[second] -- f }; +\end{codeexample} + % + \item \declare{|counterclockwise|} + + As for |center|, we project the nodes of the component onto the + shift line. The alignment line is now chosen so that it goes + through the center of the node whose center has the highest + projected value. + % +\begin{codeexample}[preamble={\usetikzlibrary{graphs,graphdrawing} + \usegdlibrary{trees}}] +\tikz \graph [binary tree layout, nodes={draw}, + component align=counterclockwise] + { a, b -- c, d -- e[second] -- f }; +\end{codeexample} + % +\begin{codeexample}[preamble={\usetikzlibrary{graphs,graphdrawing} + \usegdlibrary{trees}}] +\tikz \graph [binary tree layout, nodes={draw}, + component direction=90, + component align=counterclockwise] + { a, b -- c, d -- e[second] -- f }; +\end{codeexample} + The name |counterclockwise| is intended to indicate that the align + line goes through the node that comes last if we go from the + alignment direction in a counter-clockwise direction. + \item \declare{|clockwise|} + + Works like |counterclockwise|, only in the other direction: + % +\begin{codeexample}[preamble={\usetikzlibrary{graphs,graphdrawing} + \usegdlibrary{trees}}] +\tikz \graph [binary tree layout, nodes={draw}, + component align=clockwise] + { a, b -- c, d -- e[second] -- f }; +\end{codeexample} + % +\begin{codeexample}[preamble={\usetikzlibrary{graphs,graphdrawing} + \usegdlibrary{trees}}] +\tikz \graph [binary tree layout, nodes={draw}, + component direction=90, + component align=clockwise] + { a, b -- c, d -- e[second] -- f }; +\end{codeexample} + % + \item \declare{|counterclockwise bounding box|} + + This method is quite similar to |counterclockwise|, only the + alignment line does not go through the center of the node with a + maximum projected value on the shift line, but through the maximum + value of the projected bounding boxes. For a left-to-right + packing, this means that the components are aligned so that the + bounding boxes of the components are aligned at the top. + % +\begin{codeexample}[preamble={\usetikzlibrary{graphs,graphdrawing} + \usegdlibrary{trees}}] +\tikz \graph [tree layout, nodes={draw, align=center}, + component sep=0pt, + component align=counterclockwise] + { a, "high\\node" -- b};\quad +\tikz \graph [tree layout, nodes={draw, align=center}, + component sep=0pt, + component align=counterclockwise bounding box] + { a, "high\\node" -- b}; +\end{codeexample} + % + \item \declare{|clockwise bounding box|} + + Works like |counterclockwise bounding box|. + \end{itemize} + "]] +} + +--- + +declare { + key = "components go right top aligned", + use = { + { key = "component direction", value = 0}, + { key = "component align", value = "counterclockwise"}, + }, + + summary = [[" + Shorthand for |component direction=right| and + |component align=counterclockwise|. This means that, as the name + suggest, the components will be placed left-to-right and they are + aligned such that their top nodes are in a line. + "]], + examples = [[" + \tikz \graph [tree layout, nodes={draw, align=center}, + components go right top aligned] + { a, "high\\node" -- b}; + "]] +} + +--- + +declare { + key = "components go right absolute top aligned", + use = { + { key = "component direction", value=0}, + { key = "component align", value = "counterclockwise bounding box"}, + }, + + summary = [[" + Like |components go right top aligned|, but with + |component align| set to |counterclockwise| |bounding box|. + This means that the components will be aligned with their bounding + boxed being top-aligned. + "]], + examples = [[" + \tikz \graph [tree layout, nodes={draw, align=center}, + components go right absolute top aligned] + { a, "high\\node" -- b}; + "]] +} + +--- + +declare { + key = "components go right bottom aligned", + use = { + { key = "component direction", value=0}, + { key = "component align", value = "clockwise"}, + }, + + summary = "See the other |components go ...| keys." +} + +--- +-- + +declare { + key = "components go right absolute bottom aligned", + use = { + { key = "component direction", value=0}, + { key = "component align", value = "clockwise bounding box"}, + }, + + summary = "See the other |components go ...| keys." +} + + +--- + +declare { + key = "components go right center aligned", + use = { + { key = "component direction", value=0}, + { key = "component align", value = "center"}, + }, + + summary = "See the other |components go ...| keys." +} + + +--- + +declare { + key = "components go right", + use = { + { key = "component direction", value=0}, + { key = "component align", value = "first node"}, + }, + + summary = [[" + Shorthand for |component direction=right| and + |component align=first node|. + "]] + } + + +--- + +declare { + key = "components go left top aligned", + use = { + { key = "component direction", value=180}, + { key = "component align", value = "clockwise"}, + }, + + summary = "See the other |components go ...| keys.", + + examples = [[" + \tikz \graph [tree layout, nodes={draw, align=center}, + components go left top aligned] + { a, "high\\node" -- b}; + "]] +} + +--- +-- + +declare { + key = "components go left absolute top aligned", + use = { + { key = "component direction", value=180}, + { key = "component align", value = "clockwise bounding box"}, + }, + + summary = "See the other |components go ...| keys." +} + + +--- +-- + +declare { + key = "components go left bottom aligned", + use = { + { key = "component direction", value=180}, + { key = "component align", value = "counterclockwise"}, + }, + + summary = "See the other |components go ...| keys." +} + + +--- +-- + +declare { + key = "components go left absolute bottom aligned", + use = { + { key = "component direction", value=180}, + { key = "component align", value = "counterclockwise bounding box"}, + }, + + summary = "See the other |components go ...| keys." +} + + +--- +-- + +declare { + key = "components go left center aligned", + use = { + { key = "component direction", value=180}, + { key = "component align", value = "center"}, + }, + summary = "See the other |components go ...| keys." +} + + +--- +-- + +declare { + key = "components go left", + use = { + { key = "component direction", value=180}, + { key = "component align", value = "first node"}, + }, + summary = "See the other |components go ...| keys." +} + + + +--- + +declare { + key = "components go down right aligned", + use = { + { key = "component direction", value=270}, + { key = "component align", value = "counterclockwise"}, + }, + summary = "See the other |components go ...| keys.", + + examples = {[[" + \tikz \graph [tree layout, nodes={draw, align=center}, + components go down left aligned] + { a, hello -- {world,s} }; + "]],[[" + \tikz \graph [tree layout, nodes={draw, align=center}, + components go up absolute left aligned] + { a, hello -- {world,s}}; + "]] + } +} + +--- +-- + +declare { + key = "components go down absolute right aligned", + use = { + { key = "component direction", value=270}, + { key = "component align", value = "counterclockwise bounding box"}, + }, + summary = "See the other |components go ...| keys." +} + + +--- +-- + +declare { + key = "components go down left aligned", + use = { + { key = "component direction", value=270}, + { key = "component align", value = "clockwise"}, + }, + summary = "See the other |components go ...| keys." +} + + +--- +-- + +declare { + key = "components go down absolute left aligned", + use = { + { key = "component direction", value=270}, + { key = "component align", value = "clockwise bounding box"}, + }, + summary = "See the other |components go ...| keys." +} + + +--- +-- + +declare { + key = "components go down center aligned", + use = { + { key = "component direction", value=270}, + { key = "component align", value = "center"}, + }, + summary = "See the other |components go ...| keys." +} + + +--- +-- + +declare { + key = "components go down", + use = { + { key = "component direction", value=270}, + { key = "component align", value = "first node"}, + }, + summary = "See the other |components go ...| keys." +} + +--- +-- + +declare { + key = "components go up right aligned", + use = { + { key = "component direction", value=90}, + { key = "component align", value = "clockwise"}, + }, + summary = "See the other |components go ...| keys." +} + + +--- +-- + +declare { + key = "components go up absolute right aligned", + use = { + { key = "component direction", value=90}, + { key = "component align", value = "clockwise bounding box"}, + }, + summary = "See the other |components go ...| keys." +} + + +--- +-- + +declare { + key = "components go up left aligned", + use = { + { key = "component direction", value=90}, + { key = "component align", value = "counterclockwise"}, + }, + summary = "See the other |components go ...| keys." +} + + +--- +-- + +declare { + key = "components go up absolute left aligned", + use = { + { key = "component direction", value=90}, + { key = "component align", value = "counterclockwise bounding box"}, + }, + summary = "See the other |components go ...| keys." +} + + +--- +-- + +declare { + key = "components go up center aligned", + use = { + { key = "component direction", value=90}, + { key = "component align", value = "center"}, + }, + summary = "See the other |components go ...| keys." +} + + +--- +-- + +declare { + key = "components go up", + use = { + { key = "component direction", value=90}, + { key = "component align", value = "first node"}, + }, + summary = "See the other |components go ...| keys." +} + + + + +return Components diff --git a/graphics/pgf/base/tex/generic/graphdrawing/lua/pgf/gd/control/ComponentDirection.lua b/graphics/pgf/base/tex/generic/graphdrawing/lua/pgf/gd/control/ComponentDirection.lua new file mode 100644 index 0000000000..f941ff226e --- /dev/null +++ b/graphics/pgf/base/tex/generic/graphdrawing/lua/pgf/gd/control/ComponentDirection.lua @@ -0,0 +1,58 @@ +-- Copyright 2012 by Till Tantau +-- +-- This file may be distributed an/or modified +-- +-- 1. under the LaTeX Project Public License and/or +-- 2. under the GNU Public License +-- +-- See the file doc/generic/pgf/licenses/LICENSE for more information + +-- @release $Header$ + + +local declare = require "pgf.gd.interface.InterfaceToAlgorithms".declare + + +--- +-- @section subsubsection {Arranging Components in a Certain Direction} +-- +-- @end + +--- + +declare { + key = "component direction", + type = "direction", + initial = "0", + + summary = [[" + The \meta{angle} is used to determine the relative position of each + component relative to the previous one. The direction need not be a + multiple of |90|. As usual, you can use texts like |up| or + |right| instead of a number. + "]], + documentation = [[" + As the examples show, the direction only has an influence on the + relative positions of the components, not on the direction of growth + inside the components. In particular, the components are not rotated + by this option in any way. You can use the |grow| option or |orient| + options to orient individual components. + "]], + examples = {[[" + \tikz \graph [tree layout, nodes={inner sep=1pt,draw,circle}, + component direction=left] + { a, b, c -- d -- e, f -- g }; + "]],[[" + \tikz \graph [tree layout, nodes={inner sep=1pt,draw,circle}, + component direction=10] + { a, b, c -- d -- e, f -- g }; + "]],[[" + \tikz \graph [tree layout, nodes={inner sep=1pt,draw,circle}, + component direction=up] + { a, b, c [grow=right] -- d -- e, f[grow=45] -- g }; + "]] + } +} + + +return Components
\ No newline at end of file diff --git a/graphics/pgf/base/tex/generic/graphdrawing/lua/pgf/gd/control/ComponentDistance.lua b/graphics/pgf/base/tex/generic/graphdrawing/lua/pgf/gd/control/ComponentDistance.lua new file mode 100644 index 0000000000..a2d04a178f --- /dev/null +++ b/graphics/pgf/base/tex/generic/graphdrawing/lua/pgf/gd/control/ComponentDistance.lua @@ -0,0 +1,108 @@ +-- Copyright 2012 by Till Tantau +-- +-- This file may be distributed an/or modified +-- +-- 1. under the LaTeX Project Public License and/or +-- 2. under the GNU Public License +-- +-- See the file doc/generic/pgf/licenses/LICENSE for more information + +-- @release $Header$ + + +local declare = require "pgf.gd.interface.InterfaceToAlgorithms".declare + + +--- +-- @section subsubsection {The Distance Between Components} +-- +-- Once the components of a graph have been oriented, sorted, aligned, +-- and a direction has been chosen, it remains to determine the distance +-- between adjacent components. Two methods are available for computing +-- this distance, as specified by the following option: +-- +-- @end + +--- + +declare { + key = "component packing", + type = "string", + initial = "skyline", + + documentation = [[" + Given two components, their distance is computed as follows in + dependence of \meta{method}: + % + \begin{itemize} + \item \declare{|rectangular|} + + Imagine a bounding box to be drawn around both components. They + are then shifted such that the padding (separating distance) + between the two boxes is the current value of |component sep|. + % + \begin{codeexample}[preamble={\usetikzlibrary{graphs,graphdrawing} + \usegdlibrary{trees}}] + \tikz \graph [tree layout, nodes={draw}, component sep=0pt, + component packing=rectangular] + { a -- long text, longer text -- b}; + \end{codeexample} + % + \item \declare{|skyline|} + + The ``skyline method'' is used to compute the distance. It works + as follows: For simplicity, assume that the component direction is + right (other case work similarly, only everything is + rotated). Imaging the second component to be placed far right + beyond the first component. Now start moving the second component + back to the left until one of the nodes of the second component + touches a node of the first component, and stop. Again, the + padding |component sep| can be used to avoid the nodes actually + touching each other. + % + \begin{codeexample}[preamble={\usetikzlibrary{graphs,graphdrawing} + \usegdlibrary{trees}}] + \tikz \graph [tree layout, nodes={draw}, component sep=0pt, + level distance=1.5cm, + component packing=skyline] + { a -- long text, longer text -- b}; + \end{codeexample} + + In order to avoid nodes of the second component ``passing through + a hole in the first component'', the actual algorithm is a bit + more complicated: For both components, a ``skyline'' is + computed. For the first component, consider an arbitrary + horizontal line. If there are one or more nodes on this line, the + rightmost point on any of the bounding boxes of these nodes will + be the point on the skyline of the first component for this + line. Similarly, for the second component, for each horizontal + level the skyline is given by the leftmost point on any of the + bounding boxes intersecting the line. + + Now, the interesting case are horizontal lines that do not + intersect any of the nodes of the first and/or second + component. Such lines represent ``holes'' in the skyline. For + them, the following rule is used: Move the horizontal line upward + and downward as little as possible until a height is reached where + there is a skyline defined. Then the skyline position on the + original horizontal line is the skyline position at the reached + line, minus (or, for the second component, plus) the distance by + which the line was moved. This means that the holes are ``filled + up by slanted roofs''. + % + \begin{codeexample}[preamble={\usetikzlibrary{graphs,graphdrawing} + \usegdlibrary{trees}}] + \begin{tikzpicture} + \graph [tree layout, nodes={draw}, component sep=0pt, + component packing=skyline] + { a -- long text, longer text -- b}; + \draw[red] (long text.north east) -- ++(north west:1cm); + \end{tikzpicture} + \end{codeexample} + + \end{itemize} + "]] +} + + +return Components diff --git a/graphics/pgf/base/tex/generic/graphdrawing/lua/pgf/gd/control/ComponentOrder.lua b/graphics/pgf/base/tex/generic/graphdrawing/lua/pgf/gd/control/ComponentOrder.lua new file mode 100644 index 0000000000..78537b614c --- /dev/null +++ b/graphics/pgf/base/tex/generic/graphdrawing/lua/pgf/gd/control/ComponentOrder.lua @@ -0,0 +1,105 @@ +-- Copyright 2012 by Till Tantau +-- +-- This file may be distributed an/or modified +-- +-- 1. under the LaTeX Project Public License and/or +-- 2. under the GNU Public License +-- +-- See the file doc/generic/pgf/licenses/LICENSE for more information + +-- @release $Header$ + + +local declare = require "pgf.gd.interface.InterfaceToAlgorithms".declare + + + + +--- +-- @section subsubsection {Ordering the Components} +-- +-- The different connected components of the graph are collected in a +-- list. The ordering of the nodes in this list can be configured using +-- the following key. +-- +-- @end + + +--- + +declare { + key = "component order", + type = "string", + initial = "by first specified node", + + summary = [[" + Selects a ``strategy'' for ordering the components. By default, + they are ordered in the way they appear in the input. + "]], + documentation = [[" + The following values are permissible for \meta{strategy} + % + \begin{itemize} + \item \declare{|by first specified node|} + + The components are ordered ``in the way they appear in the input + specification of the graph''. More precisely, for each component + consider the node that is first encountered in the description + of the graph. Order the components in the same way as these nodes + appear in the graph description. + \item \declare{|increasing node number|} + + The components are ordered by increasing number of nodes. For + components with the same number of nodes, the first node in each + component is considered and they are ordered according to the + sequence in which these nodes appear in the input. + + \item \declare{|decreasing node number|} + As above, but in decreasing order. + \end{itemize} + "]], + examples = {[[" + \tikz \graph [tree layout, nodes={inner sep=1pt,draw,circle}, + component order=by first specified node] + { a, b, c, f -- g, c -- d -- e }; + "]],[[" + \tikz \graph [tree layout, nodes={inner sep=1pt,draw,circle}, + component order=increasing node number] + { a, b, c -- d -- e, f -- g }; + "]] + } +} + + +--- + +declare { + key = "small components first", + use = { + { key = "component order", value = "increasing node number" } + }, + + summary = [[" + A shorthand for |component order=increasing node number|. + "]] + } + +--- + +declare { + key = "large components first", + use = { + { key = "component order", value = "decreasing node number" }, + }, + summary = [[" + A shorthand for |component order=decreasing node number|. + "]], + examples = [[" + \tikz \graph [tree layout, nodes={inner sep=1pt,draw,circle}, + large components first] + { a, b, c -- d -- e, f -- g }; + "]] +} + + +return Components
\ No newline at end of file diff --git a/graphics/pgf/base/tex/generic/graphdrawing/lua/pgf/gd/control/Components.lua b/graphics/pgf/base/tex/generic/graphdrawing/lua/pgf/gd/control/Components.lua new file mode 100644 index 0000000000..c5019a56e2 --- /dev/null +++ b/graphics/pgf/base/tex/generic/graphdrawing/lua/pgf/gd/control/Components.lua @@ -0,0 +1,127 @@ +-- Copyright 2012 by Till Tantau +-- +-- This file may be distributed an/or modified +-- +-- 1. under the LaTeX Project Public License and/or +-- 2. under the GNU Public License +-- +-- See the file doc/generic/pgf/licenses/LICENSE for more information + +-- @release $Header$ + + +-- Imports + +local declare = require "pgf.gd.interface.InterfaceToAlgorithms".declare + + +--- +-- @section subsection {Packing of Connected Components} +-- +-- \label{subsection-gd-component-packing} +-- \label{section-gd-packing} +-- +-- Graphs may be composed of subgraphs or \emph{components} that are not +-- connected to each other. In order to draw these nicely, most graph +-- drawing algorithms split them into separate graphs, compute +-- their layouts with the same graph drawing algorithm independently and, +-- in a postprocessing step, arrange them next to each other. Note, +-- however, that some graph drawing algorithms can also arrange the nodes +-- of the graph in a uniform way even for unconnected components (the +-- |simple necklace layout| is a case in point); for such algorithms you can +-- choose whether they should be applied to each component individually +-- or not (if not, the following options do not apply). To configure +-- which is the case, use the |componentwise| key. +-- +-- The default method for placing the different components works as +-- follows: +-- % +-- \begin{enumerate} +-- \item For each component, a layout is determined and the component is +-- oriented as described +-- Section~\ref{subsection-library-graphdrawing-standard-orientation} +-- on the orientation of graphs. +-- \item The components are sorted as prescribed by the +-- |component order| key. +-- \item The first component is now placed (conceptually) at the +-- origin. (The final position of this and all other components will be +-- determined later, namely in the anchoring phase, but let us imagine +-- that the first component lies at the origin at this point.) +-- \item The second component is now positioned relative to the first +-- component. The ``direction'' in which the next component is placed +-- relative to the first one is determined by the |component direction| +-- key, so components can be placed from left to right or up to down or +-- in any other direction (even something like $30^\circ$). However, +-- both internally and in the following description, we assume that the +-- components are placed from left to right; other directions are +-- achieved by doing some (clever) rotating of the arrangement achieved +-- in this way. +-- +-- So, we now wish to place the second component to the right of the +-- first component. The component is first shifted vertically according +-- to some alignment strategy. For instance, it can be shifted so that +-- the topmost node of the first component and the topmost node of the +-- second component have the same vertical position. Alternatively, we +-- might require that certain ``alignment nodes'' in both components +-- have the same vertical position. There are several other strategies, +-- which can be configured using the |component align| key. +-- +-- One the vertical position has been fixed, the horizontal position is +-- computed. Here, two different strategies are available: First, image +-- rectangular bounding boxed to be drawn around both components. Then +-- we shift the second component such that the right border of the +-- bounding box of the first component touches the left border of the +-- bounding box of the second component. Instead of having the bounding +-- boxes ``touch'', we can also have a padding of |component sep| +-- between them. The second strategy is more involved and also known as +-- a ``skyline'' strategy, where (roughly) the components are +-- ``moved together as near as possible so that nodes do not touch''. +-- \item +-- After the second component has been placed, the third component is +-- considered and positioned relative to the second one, and so on. +-- \item +-- At the end, as hinted at earlier, the whole arrangement is rotate so +-- that instead of ``going right'' the component go in the direction of +-- |component direction|. Note, however, that this rotation applies only +-- to the ``shift'' of the components; the components themselves are +-- not rotated. Fortunately, this whole rotation process happens in the +-- background and the result is normally exactly what you would expect. +-- \end{enumerate} +-- +-- In the following, we go over the different keys that can be used to +-- configure the component packing. +-- +-- @end + + +--- + +declare { + key = "componentwise", + type = "boolean", + + summary = [[" + For algorithms that also support drawing unconnected graphs, use + this key to enforce that the components of the graph are, + nevertheless, laid out individually. For algorithms that do not + support laying out unconnected graphs, this option has no effect; + rather it works as if this option were always set. + "]], + examples = {[[" + \tikz \graph [simple necklace layout] + { + a -- b -- c -- d -- a, + 1 -- 2 -- 3 -- 1 + }; + "]],[[", + \tikz \graph [simple necklace layout, componentwise] + { + a -- b -- c -- d -- a, + 1 -- 2 -- 3 -- 1 + }; + "]] + } +} + + + diff --git a/graphics/pgf/base/tex/generic/graphdrawing/lua/pgf/gd/control/Distances.lua b/graphics/pgf/base/tex/generic/graphdrawing/lua/pgf/gd/control/Distances.lua new file mode 100644 index 0000000000..6dc5a13018 --- /dev/null +++ b/graphics/pgf/base/tex/generic/graphdrawing/lua/pgf/gd/control/Distances.lua @@ -0,0 +1,454 @@ +-- Copyright 2012 by Till Tantau +-- +-- This file may be distributed an/or modified +-- +-- 1. under the LaTeX Project Public License and/or +-- 2. under the GNU Public License +-- +-- See the file doc/generic/pgf/licenses/LICENSE for more information + +-- @release $Header$ + + +local declare = require "pgf.gd.interface.InterfaceToAlgorithms".declare +local lib = require "pgf.gd.lib" + +--- +-- @section subsection {Padding and Node Distances} +-- +-- \label{subsection-gd-dist-pad} +-- +-- In many drawings, you may wish to specify how ``near'' two nodes should +-- be placed by a graph drawing algorithm. Naturally, this depends +-- strongly on the specifics of the algorithm, but there are a number of +-- general keys that will be used by many algorithms. +-- +-- There are different kinds of objects for which you can specify +-- distances and paddings: +-- % +-- \begin{itemize} +-- \item You specify the ``natural'' distance between nodes +-- connected by an edge using |node distance|, which is also available in +-- normal \tikzname\ albeit for a slightly different purpose. However, +-- not every algorithm will (or can) honor the key; see the description +-- of each algorithm what it will ``make of this option''. +-- \item A number of graph drawing algorithms arrange nodes in layers +-- (or levels); we refer +-- to the nodes on the same layer as siblings (although, in a tree, +-- siblings are only nodes with the same parent; nevertheless we use +-- ``sibling'' loosely also for nodes that are more like ``cousins''). +-- \item When a graph consists of several connected component, many graph +-- drawing algorithms will layout these components individually. The +-- different components will then be arranged next to each other, see +-- Section~\ref{section-gd-packing} for the details, such that between +-- the nodes of any two components a padding is available. +-- \end{itemize} +-- +-- @end + + + + +--- + +declare { + key = "node distance", + type = "length", + initial = "1cm", + + summary = [[" + This is minimum distance that the centers of nodes connected by an + edge should have. It will not always be possible to satisfy this + desired distance, for instance in case the nodes are too big. In + this case, the \meta{length} is just considered as a lower bound. + "]], + examples = [[" + \begin{tikzpicture} + \graph [simple necklace layout, node distance=1cm, node sep=0pt, + nodes={draw,circle,as=.}] + { + 1 -- 2 [minimum size=2cm] -- 3 -- + 4 -- 5 -- 6 -- 7 --[orient=up] 8 + }; + \draw [red,|-|] (1.center) -- ++(0:1cm); + \draw [red,|-|] (5.center) -- ++(180:1cm); + \end{tikzpicture} + "]] +} + + +--- + +declare { + key = "node pre sep", + type = "length", + initial = ".333em", + + summary = [[" + This is a minimum ``padding'' or ``separation'' between the border + of nodes connected by an edge. Thus, if nodes are so big that nodes + with a distance of |node distance| would overlap (or + just come with \meta{dimension} distance of one another), their + distance is enlarged so that this distance is still satisfied. + The |pre| means that the padding is added to the node ``at the + front''. This make sense only for some algorithms, like for a + simple necklace layout. + "]], + examples = {[[" + \tikz \graph [simple necklace layout, node distance=0cm, nodes={circle,draw}] + { 1--2--3--4--5--1 }; + "]],[[" + \tikz \graph [simple necklace layout, node distance=0cm, node sep=0mm, + nodes={circle,draw}] + { 1--2--3[node pre sep=5mm]--4--5[node pre sep=1mm]--1 }; + "]] + } +} + +--- + +declare { + key = "node post sep", + type = "length", + initial = ".333em", + + summary = [[" + Works like |node pre sep|. + "]] +} + + + +--- +-- @param length A length. + +declare { + key = "node sep", + type = "length", + use = { + { key = "node pre sep", value = function(v) return v/2 end }, + { key = "node post sep", value = function(v) return v/2 end }, + }, + summary = [[" + A shorthand for setting both |node pre sep| and |node post sep| to + $\meta{length}/2$. + "]] +} + + +--- + +declare { + key = "level distance", + type = "length", + initial = "1cm", + + summary = [[" + This is minimum distance that the centers of nodes on one + level should have from the centers of nodes on the next level. It + will not always be possible to satisfy this desired distance, for + instance in case the nodes are too big. In this case, the + \meta{length} is just considered as a lower bound. + "]], + examples = [[" + \begin{tikzpicture}[inner sep=2pt] + \draw [help lines] (0,0) grid (3.5,2); + \graph [layered layout, level distance=1cm, level sep=0] + { 1 [x=1,y=2] -- 2 -- 3 -- 1 }; + \graph [layered layout, level distance=5mm, level sep=0] + { 1 [x=3,y=2] -- 2 -- 3 -- 1, 3 -- {4,5} -- 6 -- 3 }; + \end{tikzpicture} + "]] +} + +--- +declare { + key = "layer distance", + type = "length", + use = { + { key = "level distance", value = lib.id }, + }, + summary = "An alias for |level distance|" +} + +--- +declare { + key = "level pre sep", + type = "length", + initial = ".333em", + + summary = [[" + This is a minimum ``padding'' or ``separation'' between the border + of the nodes on a level to any nodes on the previous level. Thus, if + nodes are so big that nodes on consecutive levels would overlap (or + just come with \meta{length} distance of one another), their + distance is enlarged so that this distance is still satisfied. + If a node on the previous level also has a |level post sep|, this + post padding and the \meta{dimension} add up. Thus, these keys + behave like the ``padding'' keys rather + than the ``margin'' key of cascading style sheets. + "]], + examples = [[" + \begin{tikzpicture}[inner sep=2pt, level sep=0pt, sibling distance=0pt] + \draw [help lines] (0,0) grid (3.5,2); + \graph [layered layout, level distance=0cm, nodes=draw] + { 1 [x=1,y=2] -- {2,3[level pre sep=1mm],4[level pre sep=5mm]} -- 5 }; + \graph [layered layout, level distance=0cm, nodes=draw] + { 1 [x=3,y=2] -- {2,3,4} -- 5[level pre sep=5mm] }; + \end{tikzpicture} + "]] +} + +--- + +declare { + key = "level post sep", + type = "length", + initial = ".333em", + + summary = [[" + Works like |level pre sep|. + "]] +} + +--- +declare { + key = "layer pre sep", + type = "length", + use = { + { key = "level pre sep", value = lib.id }, + }, + summary = "An alias for |level pre sep|." +} + +--- +declare { + key = "layer post sep", + type = "length", + use = { + { key = "level post sep", value = lib.id }, + }, + summary = "An alias for |level post sep|." +} + + + + +--- +-- @param length A length + +declare { + key = "level sep", + type = "length", + use = { + { key = "level pre sep", value = function (v) return v/2 end }, + { key = "level post sep", value = function (v) return v/2 end }, + }, + + summary = [[" + A shorthand for setting both |level pre sep| and |level post sep| to + $\meta{length}/2$. Note that if you set |level distance=0| and + |level sep=1em|, you get a layout where any two consecutive layers + are ``spaced apart'' by |1em|. + "]] +} + + +--- +declare { + key = "layer sep", + type = "number", + use = { + { key = "level sep", value = lib.id }, + }, + summary = "An alias for |level sep|." +} + + +--- +declare { + key = "sibling distance", + type = "length", + initial = "1cm", + + summary = [[" + This is minimum distance that the centers of node should have to the + center of the next node on the same level. As for levels, this is + just a lower bound. + For some layouts, like a simple necklace layout, the \meta{length} is + measured as the distance on the circle. + "]], + examples = {[[" + \tikz \graph [tree layout, sibling distance=1cm, nodes={circle,draw}] + { 1--{2,3,4,5} }; + "]],[[" + \tikz \graph [tree layout, sibling distance=0cm, sibling sep=0pt, + nodes={circle,draw}] + { 1--{2,3,4,5} }; + "]],[[" + \tikz \graph [tree layout, sibling distance=0cm, sibling sep=0pt, + nodes={circle,draw}] + { 1--{2,3[sibling distance=1cm],4,5} }; + "]] + } +} + + +--- + +declare { + key = "sibling pre sep", + type = "length", + initial = ".333em", + + summary = [[" + Works like |level pre sep|, only for siblings. + "]], + examples = [[" + \tikz \graph [tree layout, sibling distance=0cm, nodes={circle,draw}, + sibling sep=0pt] + { 1--{2,3[sibling pre sep=1cm],4,5} }; + "]] +} + +--- + +declare { + key = "sibling post sep", + type = "length", + initial = ".333em", + + summary = [[" + Works like |sibling pre sep|. + "]] + } + + + +--- +-- @param length A length + +declare { + key = "sibling sep", + type = "length", + use = { + { key = "sibling pre sep", value = function(v) return v/2 end }, + { key = "sibling post sep", value = function(v) return v/2 end }, + }, + + summary = [[" + A shorthand for setting both |sibling pre sep| and |sibling post sep| to + $\meta{length}/2$. + "]] +} + + + + + + +--- +declare { + key = "part distance", + type = "length", + initial = "1.5cm", + + summary = [[" + This is minimum distance between the centers of ``parts'' of a + graph. What a ``part'' is depends on the algorithm. + "]] +} + + +--- + +declare { + key = "part pre sep", + type = "length", + initial = "1em", + summary = "A pre-padding for parts." +} + +--- + +declare { + key = "part post sep", + type = "length", + initial = "1em", + summary = "A post-padding for pars." + } + + + +--- +-- @param length A length + +declare { + key = "part sep", + type = "length", + use = { + { key = "part pre sep", value = function(v) return v/2 end }, + { key = "part post sep", value = function(v) return v/2 end }, + }, + + summary = [[" + A shorthand for setting both |part pre sep| and |part post sep| to + $\meta{length}/2$. + "]] +} + + + + +--- + +declare { + key = "component sep", + type = "length", + initial = "1.5em", + + summary = [[" + This is padding between the bounding boxes that nodes of different + connected components will have when they are placed next to each + other. + "]], + examples = {[[" + \tikz \graph [binary tree layout, sibling distance=4mm, level distance=8mm, + components go right top aligned, + component sep=1pt, nodes=draw] + { + 1 -> 2 -> {3->4[second]->5,6,7}; + a -> b[second] -> c[second] -> d -> e; + x -> y[second] -> z -> u[second] -> v; + }; + "]],[[" + \tikz \graph [binary tree layout, sibling distance=4mm, level distance=8mm, + components go right top aligned, + component sep=1em, nodes=draw] + { + 1 -> 2 -> {3->4[second]->5,6,7}; + a -> b[second] -> c[second] -> d -> e; + x -> y[second] -> z -> u[second] -> v; + }; + "]] + } +} + + + +--- + +declare { + key = "component distance", + type = "length", + initial = "2cm", + + summary = [[" + This is the minimum distance between the centers of bounding + boxes of connected components when they are placed next to each + other. (Not used, currently.) + "]] +} + + +return Distances diff --git a/graphics/pgf/base/tex/generic/graphdrawing/lua/pgf/gd/control/FineTune.lua b/graphics/pgf/base/tex/generic/graphdrawing/lua/pgf/gd/control/FineTune.lua new file mode 100644 index 0000000000..87d67b5b56 --- /dev/null +++ b/graphics/pgf/base/tex/generic/graphdrawing/lua/pgf/gd/control/FineTune.lua @@ -0,0 +1,164 @@ +-- Copyright 2012 by Till Tantau +-- +-- This file may be distributed an/or modified +-- +-- 1. under the LaTeX Project Public License and/or +-- 2. under the GNU Public License +-- +-- See the file doc/generic/pgf/licenses/LICENSE for more information + +-- @release $Header$ + + +local declare = require "pgf.gd.interface.InterfaceToAlgorithms".declare + +local Coordinate = require "pgf.gd.model.Coordinate" +local lib = require "pgf.gd.lib" + +--- +-- @section subsection {Fine-Tuning Positions of Nodes} +-- +-- @end + + + +--- +declare { + key = "nudge", + type = "canvas coordinate", + + summary = [[" + This option allows you to slightly ``nudge'' (move) nodes after + they have been positioned by the given offset. The idea is that + this nudging is done after the position of the node has been + computed, so nudging has no influence on the actual graph + drawing algorithms. This, in turn, means that you can use + nudging to ``correct'' or ``optimize'' the positioning of nodes + after the algorithm has computed something. + "]], + + examples = [[" + \tikz \graph [edges=rounded corners, nodes=draw, + layered layout, sibling distance=0] { + a -- {b, c, d[nudge=(up:2mm)]} -- e -- a; + }; + "]] +} + + +--- +-- @param distance A distance by which the node is nudges. + +declare { + key = "nudge up", + type = "length", + use = { + { key = "nudge", value = function (v) return Coordinate.new(0,v) end } + }, + + summary = "A shorthand for nudging a node upwards.", + examples = [[" + \tikz \graph [edges=rounded corners, nodes=draw, + layered layout, sibling distance=0] { + a -- {b, c, d[nudge up=2mm]} -- e -- a; + }; + "]] +} + + +--- +-- @param distance A distance by which the node is nudges. + +declare { + key = "nudge down", + type = "length", + use = { + { key = "nudge", value = function (v) return Coordinate.new(0,-v) end } + }, + + summary = "Like |nudge up|, but downwards." +} + +--- +-- @param distance A distance by which the node is nudges. + +declare { + key = "nudge left", + type = "length", + use = { + { key = "nudge", value = function (v) return Coordinate.new(-v,0) end } + }, + + summary = "Like |nudge up|, but left.", + examples = [[" + \tikz \graph [edges=rounded corners, nodes=draw, + layered layout, sibling distance=0] { + a -- {b, c, d[nudge left=2mm]} -- e -- a; + }; + "]] +} + +--- +-- @param distance A distance by which the node is nudges. + +declare { + key = "nudge right", + type = "length", + use = { + { key = "nudge", value = function (v) return Coordinate.new(v,0) end } + }, + + summary = "Like |nudge left|, but right." +} + + +--- +declare { + key = "regardless at", + type = "canvas coordinate", + + summary = [[" + Using this option you can provide a position for a node to wish + it will be forced after the graph algorithms have run. So, the node + is positioned normally and the graph drawing algorithm does not know + about the position specified using |regardless at|. However, + afterwards, the node is placed there, regardless of what the + algorithm has computed (all other nodes are unaffected). + "]], + examples = [[" + \tikz \graph [edges=rounded corners, nodes=draw, + layered layout, sibling distance=0] { + a -- {b,c,d[regardless at={(1,0)}]} -- e -- a; + }; + "]] +} + + + + +--- +-- @param pos A canvas position (a coordinate). + +declare { + key = "nail at", + type = "canvas coordinate", + use = { + { key = "desired at", value = lib.id }, + { key = "regardless at", value = lib.id }, + }, + + summary = [[" + This option combines |desired at| and |regardless at|. Thus, the + algorithm is ``told'' about the desired position. If it fails to place + the node at the desired position, it will be put there + regardless. The name of the key is intended to remind one of a node + being ``nailed'' to the canvas. + "]], + examples = [[" + \tikz \graph [edges=rounded corners, nodes=draw, + layered layout, sibling distance=0] { + a -- {b,c,d[nail at={(1,0)}]} -- e[nail at={(1.5,-1)}] -- a; + }; + "]] +} + diff --git a/graphics/pgf/base/tex/generic/graphdrawing/lua/pgf/gd/control/LayoutPipeline.lua b/graphics/pgf/base/tex/generic/graphdrawing/lua/pgf/gd/control/LayoutPipeline.lua new file mode 100644 index 0000000000..1c5e48b4c7 --- /dev/null +++ b/graphics/pgf/base/tex/generic/graphdrawing/lua/pgf/gd/control/LayoutPipeline.lua @@ -0,0 +1,1347 @@ +-- Copyright 2012 by Till Tantau +-- +-- This file may be distributed an/or modified +-- +-- 1. under the LaTeX Project Public License and/or +-- 2. under the GNU Public License +-- +-- See the file doc/generic/pgf/licenses/LICENSE for more information + +-- @release $Header$ + + +--- +-- This class controls the running of graph drawing algorithms on +-- graphs. In particular, it performs pre- and posttransformations and +-- also invokes the collapsing of sublayouts. +-- +-- You do not call any of the methods of this class directly, the +-- whole class is included only for documentation purposes. +-- +-- Before an algorithm is applied, a number of transformations will +-- have been applied, depending on the algorithm's |preconditions| +-- field: +-- % +-- \begin{itemize} +-- \item |connected| +-- +-- If this property is set for an algorithm (that is, in the +-- |declare| statement for the algorithm the |predconditions| field +-- has the entry |connected=true| set), then the graph will be +-- decomposed into connected components. The algorithm is run on each +-- component individually. +-- \item |tree| +-- +-- When set, the field |spanning_tree| of the algorithm will be set +-- to a spanning tree of the graph. This option implies |connected|. +-- \item |loop_free| +-- +-- When set, all loops (arcs from a vertex to itself) will have been +-- removed when the algorithm runs. +-- +-- \item |at_least_two_nodes| +-- +-- When explicitly set to |false| (this precondition is |true| by +-- default), the algorithm will even be run if there is only a +-- single vertex in the graph. +-- \end{itemize} +-- +-- Once the algorithm has run, the algorithm's |postconditions| will +-- be processed: +-- % +-- \begin{itemize} +-- \item |upward_oriented| +-- +-- When set, the algorithm tells the layout pipeline that the graph +-- has been laid out in a layered manner with each layer going from +-- left to right and layers at a whole going upwards (positive +-- $y$-coordinates). The graph will then be rotated and possibly +-- swapped in accordance with the |grow| key set by the user. +-- \item |fixed| +-- +-- When set, no rotational postprocessing will be done after the +-- algorithm has run. Usually, a graph is rotated to meet a user's +-- |orient| settings. However, when the algorithm has already +-- ``ideally'' rotated the graph, set this postcondition. +-- \end{itemize} +-- +-- +-- In addition to the above-described always-present and automatic +-- transformations, users may also specify additional pre- and +-- posttransformations. This happens when users install additional +-- algorithms in appropriate phases. In detail, the following happens +-- in order: +-- % +-- \begin{enumerate} +-- \item If specified, the graph is decomposed into connected +-- components and the following steps are applied to each component +-- individually. +-- \item All algorithms in the phase stack for the phase +-- |preprocessing| are applied to the component. These algorithms are +-- run one after the other in the order they appear in the phase stack. +-- \item If necessary, the spanning tree is now computed and +-- rotational information is gathered. +-- \item The single algorithm in phase |main| is called. +-- \item All algorithms in the phase stack for the phase +-- |edge routing| are run. +-- \item All algorithms in the phase stack for phase |postprocessing| +-- are run. +-- \item Edge syncing, orientation, and anchoring are applied. +-- \end{enumerate} +-- +-- If sublayouts are used, all of the above (except for anchoring) +-- happens for each sublayout. + +local LayoutPipeline = {} + + +-- Namespace +require("pgf.gd.control").LayoutPipeline = LayoutPipeline + + +-- Imports +local Direct = require "pgf.gd.lib.Direct" +local Storage = require "pgf.gd.lib.Storage" +local Simplifiers = require "pgf.gd.lib.Simplifiers" +local LookupTable = require "pgf.gd.lib.LookupTable" +local Transform = require "pgf.gd.lib.Transform" + +local Arc = require "pgf.gd.model.Arc" +local Vertex = require "pgf.gd.model.Vertex" +local Digraph = require "pgf.gd.model.Digraph" +local Coordinate = require "pgf.gd.model.Coordinate" +local Path = require "pgf.gd.model.Path" + +local Sublayouts = require "pgf.gd.control.Sublayouts" + +local lib = require "pgf.gd.lib" + +local InterfaceCore = require "pgf.gd.interface.InterfaceCore" + + + + +-- Forward definitions + +local prepare_events + + + +-- The main ``graph drawing pipeline'' that handles the pre- and +-- postprocessing for a graph. This method is called by the display +-- interface. +-- +-- @param scope A graph drawing scope. + +function LayoutPipeline.run(scope) + + -- The pipeline... + + -- Step 1: Preparations + + -- Prepare events + prepare_events(scope.events) + + -- Step 2: Recursively layout the graph, starting with the root layout + local root_layout = assert(scope.collections[InterfaceCore.sublayout_kind][1], "no layout in scope") + + scope.syntactic_digraph = + Sublayouts.layoutRecursively (scope, + root_layout, + LayoutPipeline.runOnLayout, + { root_layout }) + + -- Step 3: Anchor the graph + LayoutPipeline.anchor(scope.syntactic_digraph, scope) + + -- Step 4: Apply regardless transforms + Sublayouts.regardless(scope.syntactic_digraph) + + -- Step 5: Cut edges + LayoutPipeline.cutEdges(scope.syntactic_digraph) + +end + + + +-- +-- This method is called by the sublayout rendering pipeline when the +-- algorithm should be invoked for an individual graph. At this point, +-- the sublayouts will already have been collapsed. +-- +-- @param scope The graph drawing scope. +-- @param algorithm_class The to-be-applied algorithm class. +-- @param layout_graph A subgraph of the syntactic digraph which is +-- restricted to the current layout and in which sublayouts have +-- been contracted to single nodes. +-- @param layout The layout to which the graph belongs. +-- +function LayoutPipeline.runOnLayout(scope, algorithm_class, layout_graph, layout) + + if #layout_graph.vertices < 1 then + return + end + + -- The involved main graphs: + local layout_copy = Digraph.new (layout_graph) --Direct.digraphFromSyntacticDigraph(layout_graph) + for _,a in ipairs(layout_graph.arcs) do + local new_a = layout_copy:connect(a.tail,a.head) + new_a.syntactic_edges = a.syntactic_edges + end + + -- Step 1: Decompose the graph into connected components, if necessary: + local syntactic_components + if algorithm_class.preconditions.tree or algorithm_class.preconditions.connected or layout_graph.options.componentwise then + syntactic_components = LayoutPipeline.decompose(layout_copy) + LayoutPipeline.sortComponents(layout_graph.options['component order'], syntactic_components) + else + -- Only one component: The graph itself... + syntactic_components = { layout_copy } + end + + -- Step 2: For all components do: + for i,syntactic_component in ipairs(syntactic_components) do + + -- Step 2.1: Reset random number generator to make sure that the + -- same graph is always typeset in the same way. + lib.randomseed(layout_graph.options['random seed']) + + local digraph = Direct.digraphFromSyntacticDigraph(syntactic_component) + + -- Step 2.3: If requested, remove loops + if algorithm_class.preconditions.loop_free then + for _,v in ipairs(digraph.vertices) do + digraph:disconnect(v,v) + end + end + + -- Step 2.4: Precompute the underlying undirected graph + local ugraph = Direct.ugraphFromDigraph(digraph) + + -- Step 2.4a: Run preprocessor + for _,class in ipairs(layout_graph.options.algorithm_phases["preprocessing stack"]) do + class.new{ + digraph = digraph, + ugraph = ugraph, + scope = scope, + layout = layout, + layout_graph = layout_graph, + syntactic_component = syntactic_component, + }:run() + end + + -- Step 2.5: Create an algorithm object + local algorithm = algorithm_class.new{ + digraph = digraph, + ugraph = ugraph, + scope = scope, + layout = layout, + layout_graph = layout_graph, + syntactic_component = syntactic_component, + } + + -- Step 2.7: Compute a spanning tree, if necessary + if algorithm_class.preconditions.tree then + local spanning_algorithm_class = syntactic_component.options.algorithm_phases["spanning tree computation"] + algorithm.spanning_tree = + spanning_algorithm_class.new{ + ugraph = ugraph, + events = scope.events + }:run() + end + + -- Step 2.8: Compute growth-adjusted sizes + algorithm.rotation_info = LayoutPipeline.prepareRotateAround(algorithm.postconditions, syntactic_component) + algorithm.adjusted_bb = Storage.newTableStorage() + LayoutPipeline.prepareBoundingBoxes(algorithm.rotation_info, algorithm.adjusted_bb, syntactic_component, syntactic_component.vertices) + + -- Step 2.9: Finally, run algorithm on this component! + if #digraph.vertices > 1 or algorithm_class.run_also_for_single_node + or algorithm_class.preconditions.at_least_two_nodes == false then + -- Main run of the algorithm: + if algorithm_class.old_graph_model then + LayoutPipeline.runOldGraphModel(scope, digraph, algorithm_class, algorithm) + else + algorithm:run () + end + end + + -- Step 2.9a: Run edge routers + for _,class in ipairs(layout_graph.options.algorithm_phases["edge routing stack"]) do + class.new{ + digraph = digraph, + ugraph = ugraph, + scope = scope, + layout = layout, + layout_graph = layout_graph, + syntactic_component = syntactic_component, + }:run() + end + + -- Step 2.9b: Run postprocessor + for _,class in ipairs(layout_graph.options.algorithm_phases["postprocessing stack"]) do + class.new{ + digraph = digraph, + ugraph = ugraph, + scope = scope, + layout = layout, + layout_graph = layout_graph, + syntactic_component = syntactic_component, + }:run() + end + + -- Step 2.10: Sync the graphs + digraph:sync() + ugraph:sync() + if algorithm.spanning_tree then + algorithm.spanning_tree:sync() + end + + -- Step 2.11: Orient the graph + LayoutPipeline.orient(algorithm.rotation_info, algorithm.postconditions, syntactic_component, scope) + end + + -- Step 3: Packing: + LayoutPipeline.packComponents(layout_graph, syntactic_components) + +end + + + + + + +--- +-- This function is called internally to perform the graph anchoring +-- procedure described in +-- Section~\ref{subsection-library-graphdrawing-anchoring}. These +-- transformations are always performed. +-- +-- @param graph A graph +-- @param scope The scope + +function LayoutPipeline.anchor(graph, scope) + + -- Step 1: Find anchor node: + local anchor_node + + local anchor_node_name = graph.options['anchor node'] + if anchor_node_name then + anchor_node = scope.node_names[anchor_node_name] + end + + if not graph:contains(anchor_node) then + anchor_node = + lib.find (graph.vertices, function (v) return v.options['anchor here'] end) or + lib.find (graph.vertices, function (v) return v.options['desired at'] end) or + graph.vertices[1] + end + + -- Sanity check + assert(graph:contains(anchor_node), "anchor node is not in graph!") + + local desired = anchor_node.options['desired at'] or graph.options['anchor at'] + local delta = desired - anchor_node.pos + + -- Step 3: Shift nodes + for _,v in ipairs(graph.vertices) do + v.pos:shiftByCoordinate(delta) + end + for _,a in ipairs(graph.arcs) do + if a.path then a.path:shiftByCoordinate(delta) end + for _,e in ipairs(a.syntactic_edges) do + e.path:shiftByCoordinate(delta) + end + end +end + + + +--- +-- This method tries to determine in which direction the graph is supposed to +-- grow and in which direction the algorithm will grow the graph. These two +-- pieces of information together produce a necessary rotation around some node. +-- This rotation is returned in a table. +-- +-- Note that this method does not actually cause a rotation to happen; this is +-- left to other method. +-- +-- @param postconditions The algorithm's postconditions. +-- @param graph An undirected graph +-- @return A table containing the computed information. + +function LayoutPipeline.prepareRotateAround(postconditions, graph) + + -- Find the vertex from which we orient + local swap = true + + local v,_,grow = lib.find (graph.vertices, function (v) return v.options["grow"] end) + + if not v and graph.options["grow"] then + v,grow,swap = graph.vertices[1], graph.options["grow"], true + end + + if not v then + v,_,grow = lib.find (graph.vertices, function (v) return v.options["grow'"] end) + swap = false + end + + if not v and graph.options["grow'"] then + v,grow,swap = graph.vertices[1], graph.options["grow'"], false + end + + if not v then + v, grow, swap = graph.vertices[1], -90, true + end + + -- Now compute the rotation + local info = {} + local growth_direction = (postconditions.upward_oriented and 90) or (postconditions.upward_oriented_swapped and 90) + + if postconditions.upward_oriented_swapped then + swap = not swap + end + + if growth_direction == "fixed" then + info.angle = 0 -- no rotation + elseif growth_direction then + info.from_node = v + info.from_angle = growth_direction/360*2*math.pi + info.to_angle = grow/360*2*math.pi + info.swap = swap + info.angle = info.to_angle - info.from_angle + else + info.from_node = v + local other = lib.find_min( + graph:outgoing(v), + function (a) + if a.head ~= v and a:eventIndex() then + return a, a:eventIndex() + end + end) + info.to_node = (other and other.head) or + (graph.vertices[1] == v and graph.vertices[2] or graph.vertices[1]) + info.to_angle = grow/360*2*math.pi + info.swap = swap + info.angle = info.to_angle - math.atan2(info.to_node.pos.y - v.pos.y, info.to_node.pos.x - v.pos.x) + end + + return info +end + + + +--- +-- Compute growth-adjusted node sizes. +-- +-- For each node of the graph, compute bounding box of the node that +-- results when the node is rotated so that it is in the correct +-- orientation for what the algorithm assumes. +-- +-- The ``bounding box'' actually consists of the fields +-- % +-- \begin{itemize} +-- \item |sibling_pre|, +-- \item |sibling_post|, +-- \item |layer_pre|, and +-- \item |layer_post|, +-- \end{itemize} +-- % +-- which correspond to ``min x'', ``min y'', ``min y'', and ``max y'' +-- for a tree growing up. +-- +-- The computation of the ``bounding box'' treats a centered circle in +-- a special way, all other shapes are currently treated like a +-- rectangle. +-- +-- @param rotation_info The table computed by the function prepareRotateAround +-- @param packing_storage A storage in which the computed distances are stored. +-- @param graph An graph +-- @param vertices An array of to-be-prepared vertices inside graph + +function LayoutPipeline.prepareBoundingBoxes(rotation_info, adjusted_bb, graph, vertices) + + local angle = assert(rotation_info.angle, "angle field missing") + local swap = rotation_info.swap + + for _,v in ipairs(vertices) do + local bb = adjusted_bb[v] + local a = angle + + if v.shape == "circle" then + a = 0 -- no rotation for circles. + end + + -- Fill the bounding box field, + bb.sibling_pre = math.huge + bb.sibling_post = -math.huge + bb.layer_pre = math.huge + bb.layer_post = -math.huge + + local c = math.cos(angle) + local s = math.sin(angle) + for _,p in ipairs(v.path:coordinates()) do + local x = p.x*c + p.y*s + local y = -p.x*s + p.y*c + + bb.sibling_pre = math.min (bb.sibling_pre, x) + bb.sibling_post = math.max (bb.sibling_post, x) + bb.layer_pre = math.min (bb.layer_pre, y) + bb.layer_post = math.max (bb.layer_post, y) + end + + -- Flip sibling per and post if flag: + if swap then + bb.sibling_pre, bb.sibling_post = -bb.sibling_post, -bb.sibling_pre + end + end +end + + + + + +-- +-- Rotate the whole graph around a point +-- +-- Causes the graph to be rotated around \meta{around} so that what +-- used to be the |from_angle| becomes the |to_angle|. If the flag |swap| +-- is set, the graph is additionally swapped along the |to_angle|. +-- +-- @param graph The to-be-rotated (undirected) graph +-- @param around_x The $x$-coordinate of the point around which the graph should be rotated +-- @param around_y The $y$-coordinate +-- @param from An ``old'' angle +-- @param to A ``new'' angle +-- @param swap A boolean that, when true, requests that the graph is +-- swapped (flipped) along the new angle + +function LayoutPipeline.rotateGraphAround(graph, around_x, around_y, from, to, swap) + + -- Translate to origin + local t = Transform.new_shift(-around_x, -around_y) + + -- Rotate to zero degrees: + t = Transform.concat(Transform.new_rotation(-from), t) + + -- Swap + if swap then + t = Transform.concat(Transform.new_scaling(1,-1), t) + end + + -- Rotate to from degrees: + t = Transform.concat(Transform.new_rotation(to), t) + + -- Translate back + t = Transform.concat(Transform.new_shift(around_x, around_y), t) + + for _,v in ipairs(graph.vertices) do + v.pos:apply(t) + end + for _,a in ipairs(graph.arcs) do + for _,p in ipairs(a:pointCloud()) do + p:apply(t) + end + end +end + + + +-- +-- Orient the whole graph using two nodes +-- +-- The whole graph is rotated so that the line from the first node to +-- the second node has the given angle. If swap is set to true, the +-- graph is also flipped along this line. +-- +-- @param graph +-- @param first_node +-- @param seond_node +-- @param target_angle +-- @param swap + +function LayoutPipeline.orientTwoNodes(graph, first_node, second_node, target_angle, swap) + if first_node and second_node then + -- Compute angle between first_node and second_node: + local x = second_node.pos.x - first_node.pos.x + local y = second_node.pos.y - first_node.pos.y + + local angle = math.atan2(y,x) + LayoutPipeline.rotateGraphAround(graph, first_node.pos.x, + first_node.pos.y, angle, target_angle, swap) + end +end + + + +--- +-- Performs a post-layout orientation of the graph by performing the +-- steps documented in Section~\ref{subsection-library-graphdrawing-standard-orientation}. +-- +-- @param rotation_info The info record computed by the function |prepareRotateAround|. +-- @param postconditions The algorithm's postconditions. +-- @param graph A to-be-oriented graph. +-- @param scope The graph drawing scope. + +function LayoutPipeline.orient(rotation_info, postconditions, graph, scope) + + -- Sanity check + if #graph.vertices < 2 then return end + + -- Step 1: Search for global graph orient options: + local function f (orient, tail, head, flag) + if orient and head and tail then + local n1 = scope.node_names[tail] + local n2 = scope.node_names[head] + if graph:contains(n1) and graph:contains(n2) then + LayoutPipeline.orientTwoNodes(graph, n1, n2, orient/360*2*math.pi, flag) + return true + end + end + end + if f(graph.options["orient"], graph.options["orient tail"],graph.options["orient head"], false) then return end + if f(graph.options["orient'"], graph.options["orient tail"],graph.options["orient head"], true) then return end + local tail, head = string.match(graph.options["horizontal"] or "", "^(.*) to (.*)$") + if f(0, tail, head, false) then return end + local tail, head = string.match(graph.options["horizontal'"] or "", "^(.*) to (.*)$") + if f(0, tail, head, true) then return end + local tail, head = string.match(graph.options["vertical"] or "", "^(.*) to (.*)$") + if f(-90, tail, head, false) then return end + local tail, head = string.match(graph.options["vertical'"] or "", "^(.*) to (.*)$") + if f(-90, tail, head, true) then return end + + -- Step 2: Search for a node with the orient option: + for _, v in ipairs(graph.vertices) do + local function f (key, flag) + local orient = v.options[key] + local head = v.options["orient head"] + local tail = v.options["orient tail"] + + if orient and head then + local n2 = scope.node_names[head] + if graph:contains(n2) then + LayoutPipeline.orientTwoNodes(graph, v, n2, orient/360*2*math.pi, flag) + return true + end + elseif orient and tail then + local n1 = scope.node_names[tail] + if graph:contains(n1) then + LayoutPipeline.orientTwoNodes(graph, n1, v, orient/360*2*math.pi, flag) + return true + end + end + end + if f("orient", false) then return end + if f("orient'", true) then return end + end + + -- Step 3: Search for an edge with the orient option: + for _, a in ipairs(graph.arcs) do + if a:options("orient",true) then + return LayoutPipeline.orientTwoNodes(graph, a.tail, a.head, a:options("orient")/360*2*math.pi, false) + end + if a:options("orient'",true) then + return LayoutPipeline.orientTwoNodes(graph, a.tail, a.head, a:options("orient'")/360*2*math.pi, true) + end + end + + -- Step 4: Search two nodes with a desired at option: + local first, second, third + + for _, v in ipairs(graph.vertices) do + if v.options['desired at'] then + if first then + if second then + third = v + break + else + second = v + end + else + first = v + end + end + end + + if second then + local a = first.options['desired at'] + local b = second.options['desired at'] + return LayoutPipeline.orientTwoNodes(graph, first, second, math.atan2(b.y-a.y,b.x-a.x), false) + end + + -- Computed during preprocessing: + if rotation_info.from_node and postconditions.fixed ~= true then + local x = rotation_info.from_node.pos.x + local y = rotation_info.from_node.pos.y + local from_angle = rotation_info.from_angle or math.atan2(rotation_info.to_node.pos.y - y, rotation_info.to_node.pos.x - x) + + LayoutPipeline.rotateGraphAround(graph, x, y, from_angle, rotation_info.to_angle, rotation_info.swap) + end +end + + + + +--- +-- This internal function is called to decompose a graph into its +-- components. Whether or not this function is called depends on +-- whether the precondition |connected| is set for the algorithm class +-- and whether the |componentwise| key is used. +-- +-- @param graph A to-be-decomposed graph +-- +-- @return An array of graph objects that represent the connected components of the graph. + +function LayoutPipeline.decompose (digraph) + + -- The list of connected components (node sets) + local components = {} + + -- Remember, which graphs have already been visited + local visited = {} + + for _,v in ipairs(digraph.vertices) do + if not visited[v] then + -- Start a depth-first-search of the graph, starting at node n: + local stack = { v } + local component = Digraph.new { + syntactic_digraph = digraph.syntactic_digraph, + options = digraph.options + } + + while #stack >= 1 do + local tos = stack[#stack] + stack[#stack] = nil -- pop + + if not visited[tos] then + + -- Visit pos: + component:add { tos } + visited[tos] = true + + -- Push all unvisited neighbors: + for _,a in ipairs(digraph:incoming(tos)) do + local neighbor = a.tail + if not visited[neighbor] then + stack[#stack+1] = neighbor -- push + end + end + for _,a in ipairs(digraph:outgoing(tos)) do + local neighbor = a.head + if not visited[neighbor] then + stack[#stack+1] = neighbor -- push + end + end + end + end + + -- Ok, vertices will now contain all vertices reachable from n. + components[#components+1] = component + end + end + + if #components < 2 then + return { digraph } + end + + for _,c in ipairs(components) do + table.sort (c.vertices, function (u,v) return u.event.index < v.event.index end) + for _,v in ipairs(c.vertices) do + for _,a in ipairs(digraph:outgoing(v)) do + local new_a = c:connect(a.tail, a.head) + new_a.syntactic_edges = a.syntactic_edges + end + for _,a in ipairs(digraph:incoming(v)) do + local new_a = c:connect(a.tail, a.head) + new_a.syntactic_edges = a.syntactic_edges + end + end + end + + return components +end + + + + +-- Handling of component order +-- +-- LayoutPipeline are ordered according to a function that is stored in +-- a key of the |LayoutPipeline.component_ordering_functions| table +-- whose name is the graph option |component order|. +-- +-- @param component_order An ordering method +-- @param subgraphs A list of to-be-sorted subgraphs + +function LayoutPipeline.sortComponents(component_order, subgraphs) + if component_order then + local f = LayoutPipeline.component_ordering_functions[component_order] + if f then + table.sort (subgraphs, f) + end + end +end + + +-- Right now, we hardcode the functions here. Perhaps make this +-- dynamic in the future. Could easily be done on the tikzlayer, +-- actually. + +LayoutPipeline.component_ordering_functions = { + ["increasing node number"] = + function (g,h) + if #g.vertices == #h.vertices then + return g.vertices[1].event.index < h.vertices[1].event.index + else + return #g.vertices < #h.vertices + end + end, + ["decreasing node number"] = + function (g,h) + if #g.vertices == #h.vertices then + return g.vertices[1].event.index < h.vertices[1].event.index + else + return #g.vertices > #h.vertices + end + end, + ["by first specified node"] = nil, +} + + + + +local function compute_rotated_bb(vertices, angle, sep, bb) + + local r = Transform.new_rotation(-angle) + + for _,v in ipairs(vertices) do + -- Find the rotated bounding box field, + local t = Transform.concat(r,Transform.new_shift(v.pos.x, v.pos.y)) + + local min_x = math.huge + local max_x = -math.huge + local min_y = math.huge + local max_y = -math.huge + + for _,e in ipairs(v.path) do + if type(e) == "table" then + local c = e:clone() + c:apply(t) + + min_x = math.min (min_x, c.x) + max_x = math.max (max_x, c.x) + min_y = math.min (min_y, c.y) + max_y = math.max (max_y, c.y) + end + end + + -- Enlarge by sep: + min_x = min_x - sep + max_x = max_x + sep + min_y = min_y - sep + max_y = max_y + sep + + local _,_,_,_,c_x,c_y = v:boundingBox() + local center = Coordinate.new(c_x,c_y) + + center:apply(t) + + bb[v].min_x = min_x + bb[v].max_x = max_x + bb[v].min_y = min_y + bb[v].max_y = max_y + bb[v].c_y = center.y + end +end + + + +--- +-- This internal function packs the components of a graph. See +-- Section~\ref{subsection-gd-component-packing} for details. +-- +-- @param graph The graph +-- @param components A list of components + +function LayoutPipeline.packComponents(syntactic_digraph, components) + + local vertices = Storage.newTableStorage() + local bb = Storage.newTableStorage() + + -- Step 1: Preparation, rotation to target direction + local sep = syntactic_digraph.options['component sep'] + local angle = syntactic_digraph.options['component direction']/180*math.pi + + local mark = {} + for _,c in ipairs(components) do + + -- Setup the lists of to-be-considered nodes + local vs = {} + for _,v in ipairs(c.vertices) do + vs [#vs + 1] = v + end + + for _,a in ipairs(c.arcs) do + for _,p in ipairs(a:pointCloud()) do + vs [#vs + 1] = Vertex.new { pos = p } + end + end + vertices[c] = vs + + compute_rotated_bb(vs, angle, sep/2, bb) + end + + local x_shifts = { 0 } + local y_shifts = {} + + -- Step 2: Vertical alignment + for i,c in ipairs(components) do + local max_max_y = -math.huge + local max_center_y = -math.huge + local min_min_y = math.huge + local min_center_y = math.huge + + for _,v in ipairs(c.vertices) do + local info = bb[v] + max_max_y = math.max(info.max_y, max_max_y) + max_center_y = math.max(info.c_y, max_center_y) + min_min_y = math.min(info.min_y, min_min_y) + min_center_y = math.min(info.c_y, min_center_y) + end + + -- Compute alignment line + local valign = syntactic_digraph.options['component align'] + local line + if valign == "counterclockwise bounding box" then + line = max_max_y + elseif valign == "counterclockwise" then + line = max_center_y + elseif valign == "center" then + line = (max_max_y + min_min_y) / 2 + elseif valign == "clockwise" then + line = min_center_y + elseif valign == "first node" then + line = bb[c.vertices[1]].c_y + else + line = min_min_y + end + + -- Overruled? + for _,v in ipairs(c.vertices) do + if v.options['align here'] then + line = bb[v].c_y + break + end + end + + -- Ok, go! + y_shifts[i] = -line + + -- Adjust nodes: + for _,v in ipairs(vertices[c]) do + local info = bb[v] + info.min_y = info.min_y - line + info.max_y = info.max_y - line + info.c_y = info.c_y - line + end + end + + -- Step 3: Horizontal alignment + local y_values = {} + + for _,c in ipairs(components) do + for _,v in ipairs(vertices[c]) do + local info = bb[v] + y_values[#y_values+1] = info.min_y + y_values[#y_values+1] = info.max_y + y_values[#y_values+1] = info.c_y + end + end + + table.sort(y_values) + + local y_ranks = {} + local right_face = {} + for i=1,#y_values do + y_ranks[y_values[i]] = i + right_face[i] = -math.huge + end + + + + for i=1,#components-1 do + -- First, update right_face: + local touched = {} + + for _,v in ipairs(vertices[components[i]]) do + local info = bb[v] + local border = info.max_x + + for i=y_ranks[info.min_y],y_ranks[info.max_y] do + touched[i] = true + right_face[i] = math.max(right_face[i], border) + end + end + + -- Fill up the untouched entries: + local right_max = -math.huge + for i=1,#y_values do + if not touched[i] then + -- Search for next and previous touched + local interpolate = -math.huge + for j=i+1,#y_values do + if touched[j] then + interpolate = math.max(interpolate,right_face[j] - (y_values[j] - y_values[i])) + break + end + end + for j=i-1,1,-1 do + if touched[j] then + interpolate = math.max(interpolate,right_face[j] - (y_values[i] - y_values[j])) + break + end + end + right_face[i] = math.max(interpolate,right_face[i]) + end + right_max = math.max(right_max, right_face[i]) + end + + -- Second, compute the left face + local touched = {} + local left_face = {} + for i=1,#y_values do + left_face[i] = math.huge + end + for _,v in ipairs(vertices[components[i+1]]) do + local info = bb[v] + local border = info.min_x + + for i=y_ranks[info.min_y],y_ranks[info.max_y] do + touched[i] = true + left_face[i] = math.min(left_face[i], border) + end + end + + -- Fill up the untouched entries: + local left_min = math.huge + for i=1,#y_values do + if not touched[i] then + -- Search for next and previous touched + local interpolate = math.huge + for j=i+1,#y_values do + if touched[j] then + interpolate = math.min(interpolate,left_face[j] + (y_values[j] - y_values[i])) + break + end + end + for j=i-1,1,-1 do + if touched[j] then + interpolate = math.min(interpolate,left_face[j] + (y_values[i] - y_values[j])) + break + end + end + left_face[i] = interpolate + end + left_min = math.min(left_min, left_face[i]) + end + + -- Now, compute the shift. + local shift = -math.huge + + if syntactic_digraph.options['component packing'] == "rectangular" then + shift = right_max - left_min + else + for i=1,#y_values do + shift = math.max(shift, right_face[i] - left_face[i]) + end + end + + -- Adjust nodes: + x_shifts[i+1] = shift + for _,v in ipairs(vertices[components[i+1]]) do + local info = bb[v] + info.min_x = info.min_x + shift + info.max_x = info.max_x + shift + end + end + + -- Now, rotate shifts + for i,c in ipairs(components) do + local x = x_shifts[i]*math.cos(angle) - y_shifts[i]*math.sin(angle) + local y = x_shifts[i]*math.sin(angle) + y_shifts[i]*math.cos(angle) + + for _,v in ipairs(vertices[c]) do + v.pos.x = v.pos.x + x + v.pos.y = v.pos.y + y + end + end +end + + + + + + + +-- +-- Store for each begin/end event the index of +-- its corresponding end/begin event +-- +-- @param events An event list + +prepare_events = + function (events) + local stack = {} + + for i=1,#events do + if events[i].kind == "begin" then + stack[#stack + 1] = i + elseif events[i].kind == "end" then + local tos = stack[#stack] + stack[#stack] = nil -- pop + + events[tos].end_index = i + events[i].begin_index = tos + end + end + end + + + +--- +-- Cut the edges. This function handles the ``cutting'' of edges. The +-- idea is that every edge is a path going from the center of the from +-- node to the center of the target node. Now, we intersect this path +-- with the path of the start node and cut away everything before this +-- intersection. Likewise, we intersect the path with the head node +-- and, again, cut away everything following the intersection. +-- +-- These cuttings are not done if appropriate options are set. + +function LayoutPipeline.cutEdges(graph) + + for _,a in ipairs(graph.arcs) do + for _,e in ipairs(a.syntactic_edges) do + local p = e.path + p:makeRigid() + local orig = p:clone() + + if e.options['tail cut'] and e.tail.options['cut policy'] == "as edge requests" + or e.tail.options['cut policy'] == "all" then + + local vpath = e.tail.path:clone() + vpath:shiftByCoordinate(e.tail.pos) + + local x = p:intersectionsWith (vpath) + + if #x > 0 then + p:cutAtBeginning(x[1].index, x[1].time) + end + end + + if e.options['head cut'] and e.head.options['cut policy'] == "as edge requests" + or e.head.options['cut policy'] == "all" then + + local vpath = e.head.path:clone() + vpath:shiftByCoordinate(e.head.pos) + x = p:intersectionsWith (vpath) + if #x > 0 then + p:cutAtEnd(x[#x].index, x[#x].time) + else + -- Check whether there was an intersection with the original + --path: + local x2 = orig:intersectionsWith (vpath) + if #x2 > 0 then + -- Ok, after cutting the tail vertex, there is no longer + -- an intersection with the head vertex, but there used to + -- be one. This means that the vertices overlap and the + -- path should be ``inside'' them. Hmm... + if e.options['allow inside edges'] and #p > 1 then + local from = p[2] + local to = x2[1].point + p:clear() + p:appendMoveto(from) + p:appendLineto(to) + else + p:clear() + end + end + end + end + end + end +end + + + + + + +-- Deprecated stuff + +local Node = require "pgf.gd.deprecated.Node" +local Graph = require "pgf.gd.deprecated.Graph" +local Edge = require "pgf.gd.deprecated.Edge" +local Cluster = require "pgf.gd.deprecated.Cluster" + + + + + +local unique_count = 0 + +local function compatibility_digraph_to_graph(scope, g) + local graph = Graph.new() + + -- Graph options + graph.options = g.options + graph.orig_digraph = g + + -- Events + for i,e in ipairs(scope.events) do + graph.events[i] = e + end + + -- Nodes + for _,v in ipairs(g.vertices) do + if not v.name then + -- compat needs unique name + v.name = "auto generated node nameINTERNAL" .. unique_count + unique_count = unique_count + 1 + end + local minX, minY, maxX, maxY = v:boundingBox() + local node = Node.new{ + name = v.name, + tex = { + tex_node = v.tex and v.tex.stored_tex_box_number, + shape = v.shape, + minX = minX, + maxX = maxX, + minY = minY, + maxY = maxY, + }, + options = v.options, + event_index = v.event.index, + index = v.event.index, + orig_vertex = v, + } + graph:addNode(node) + graph.events[v.event.index or (#graph.events+1)] = { kind = 'node', parameters = node } + end + + -- Edges + local mark = Storage.new() + for _,a in ipairs(g.arcs) do + local da = g.syntactic_digraph:arc(a.tail, a.head) + if da then + for _,m in ipairs(da.syntactic_edges) do + if not mark[m] then + mark[m] = true + local from_node = graph:findNode(da.tail.name) + local to_node = graph:findNode(da.head.name) + local edge = graph:createEdge(from_node, to_node, m.direction, nil, m.options, nil) + edge.event_index = m.event.index + edge.orig_m = m + graph.events[m.event.index] = { kind = 'edge', parameters = edge } + end + end + end + local da = g.syntactic_digraph:arc(a.head, a.tail) + if da then + for _,m in ipairs(da.syntactic_edges) do + if not mark[m] then + mark[m] = true + local from_node = graph:findNode(da.tail.name) + local to_node = graph:findNode(da.head.name) + local edge = graph:createEdge(from_node, to_node, m.direction, nil, m.options, nil) + edge.event_index = m.event.index + edge.orig_m = m + graph.events[m.event.index] = { kind = 'edge', parameters = edge } + end + end + end + end + + table.sort(graph.edges, function(e1,e2) return e1.event_index < e2.event_index end) + for _,n in ipairs (graph.nodes) do + table.sort(n.edges, function(e1,e2) return e1.event_index < e2.event_index end) + end + + + -- Clusters + for _, c in ipairs(scope.collections['same layer'] or {}) do + cluster = Cluster.new("cluster" .. unique_count) + unique_count = unique_count+1 + graph:addCluster(cluster) + for _,v in ipairs(c.vertices) do + if g:contains(v) then + cluster:addNode(graph:findNode(v.name)) + end + end + end + + return graph +end + + +local function compatibility_graph_to_digraph(graph) + for _,n in ipairs(graph.nodes) do + n.orig_vertex.pos.x = n.pos.x + n.orig_vertex.pos.y = n.pos.y + end + for _,e in ipairs(graph.edges) do + if #e.bend_points > 0 then + local c = {} + for _,x in ipairs(e.bend_points) do + c[#c+1] = Coordinate.new (x.x, x.y) + end + e.orig_m:setPolylinePath(c) + end + end +end + + + + + +function LayoutPipeline.runOldGraphModel(scope, digraph, algorithm_class, algorithm) + + local graph = compatibility_digraph_to_graph(scope, digraph) + + algorithm.graph = graph + graph:registerAlgorithm(algorithm) + + -- If requested, remove loops + if algorithm_class.preconditions.loop_free then + Simplifiers:removeLoopsOldModel(algorithm) + end + + -- If requested, collapse multiedges + if algorithm_class.preconditions.simple then + Simplifiers:collapseMultiedgesOldModel(algorithm) + end + + if #graph.nodes > 1 then + -- Main run of the algorithm: + algorithm:run () + end + + -- If requested, expand multiedges + if algorithm_class.preconditions.simple then + Simplifiers:expandMultiedgesOldModel(algorithm) + end + + -- If requested, restore loops + if algorithm_class.preconditions.loop_free then + Simplifiers:restoreLoopsOldModel(algorithm) + end + + compatibility_graph_to_digraph(graph) +end + + + + +-- Done + +return LayoutPipeline diff --git a/graphics/pgf/base/tex/generic/graphdrawing/lua/pgf/gd/control/NodeAnchors.lua b/graphics/pgf/base/tex/generic/graphdrawing/lua/pgf/gd/control/NodeAnchors.lua new file mode 100644 index 0000000000..80aa7ef420 --- /dev/null +++ b/graphics/pgf/base/tex/generic/graphdrawing/lua/pgf/gd/control/NodeAnchors.lua @@ -0,0 +1,175 @@ +-- Copyright 2012 by Till Tantau +-- +-- This file may be distributed an/or modified +-- +-- 1. under the LaTeX Project Public License and/or +-- 2. under the GNU Public License +-- +-- See the file doc/generic/pgf/licenses/LICENSE for more information + +-- @release $Header$ + +local declare = require "pgf.gd.interface.InterfaceToAlgorithms".declare + + + +--- +-- @section subsection {Anchoring Edges} +-- +-- \label{section-gd-anchors} +-- +-- When a graph has been laid out completely, the edges between the +-- nodes must be drawn. Conceptually, an edge is ``between two +-- nodes'', but when we actually draw the node, we do not really want +-- the edge's path to start ``in the middle'' of the node; rather, we +-- want it to start ``on the border'' and also end there. +-- +-- Normally, computing such border positions for nodes is something we +-- would leave to the so-called display layer (which is typically +-- \tikzname\ and \tikzname\ is reasonably good at computing border +-- positions). However, different display layers may behave +-- differently here and even \tikzname\ fails when the node shapes are +-- very involved and the paths also. +-- +-- For these reasons, computing the anchor positions where edges start +-- and end is done inside the graph drawing system. As a user, you +-- specify a |tail anchor| and a |head anchor|, which are points +-- inside the tail and head nodes, respectively. The edge path will +-- then start and end at these points, however, they will usually be +-- shortened so that they actually start and end on the intersection +-- of the edge's path with the nodes' paths. + + +--- + +declare { + key = "tail anchor", + type = "string", + initial = "", + + summary = [[" + Specifies where in the tail vertex the edge should start. + "]], + + documentation = [[" + This is either a string or a number, interpreted as an angle + (with 90 meaning ``up''). If it is a string, when the start of + the edge is computed, we try to look up the anchor in the tail + vertex's table of anchors (some anchors get installed in this + table by the display system). If it is not found, we test + whether it is one of the special ``direction anchors'' like + |north| or |south east|. If so, we convert them into points on + the border of the node that lie in the direction of a line + starting at the center to a point on the bounding box of the + node in the designated direction. Finally, if the anchor is a + number, we use a point on the border of the node that is on a + line from the center in the specified direction. + + If the anchor is set to the empty string (which is the default), + the anchor is interpreted as the |center| anchor inside the + graph drawing system. However, a display system may choose to + make a difference between the |center| anchor and an empty + anchor (\tikzname\ does: for options like |bend left| if the + anchor is empty, the bend line starts at the border of the node, + while for the anchor set explicitly to |center| it starts at the + center). + + Note that graph drawing algorithms need not take the + setting of this option into consideration. However, the final + rendering of the edge will always take it into consideration + (only, the setting may not be very sensible if the algorithm + ignored it). + "]] +} + +--- + +declare { + key = "head anchor", + type = "string", + initial = "", + + summary = "See |tail anchor|" +} + + +--- + +declare { + key = "tail cut", + type = "boolean", + initial = true, + + summary = [[" + Decides whether the tail of an edge is ``cut'', meaning + that the edge's path will be shortened at the beginning so that + it starts only of the node's border rather than at the exact + position of the |tail anchor|, which may be inside the node. + "]] +} + + +--- + +declare { + key = "head cut", + type = "boolean", + initial = true, + + summary = "See |tail cut|." +} + + +--- + +declare { + key = "cut policy", + type = "string", + initial = "as edge requests", + + summary = "The policy for cutting edges entering or leaving a node.", + + documentation = [[" + This option is important for nodes only. It can have three + possible values: + % + \begin{itemize} + \item |as edge requests| Whether or not an edge entering or + leaving the node is cut depends on the setting of the edge's + |tail cut| and |head cut| options. This is the default. + \item |all| All edges entering or leaving the node are cut, + regardless of the edges' cut values. + \item |none| No edge entering or leaving the node is cut, + regardless of the edges' cut values. + \end{itemize} + "]] +} + + +--- +declare { + key = "allow inside edges", + type = "boolean", + initial = "true", + + summary = "Decides whether an edge between overlapping nodes should be drawn.", + + documentation = [[" + If two vertices overlap, it may happen that when you draw an + edge between them, this edges would be completely inside the two + vertices. In this case, one could either not draw them or one + could draw a sort of ``inside edge''. + "]], + + examples = { [[" + \tikz \graph [no layout, nodes={draw, minimum size=20pt}] { + a [x=0, y=0] -- b [x=15pt, y=10pt] -- c[x=40pt] + }; + "]],[[" + \tikz \graph [no layout, nodes={draw, minimum size=20pt}, + allow inside edges=false] { + a [x=0, y=0] -- b [x=15pt, y=10pt] -- c[x=40pt] + }; + "]] + } +}
\ No newline at end of file diff --git a/graphics/pgf/base/tex/generic/graphdrawing/lua/pgf/gd/control/Orientation.lua b/graphics/pgf/base/tex/generic/graphdrawing/lua/pgf/gd/control/Orientation.lua new file mode 100644 index 0000000000..1890fa4f39 --- /dev/null +++ b/graphics/pgf/base/tex/generic/graphdrawing/lua/pgf/gd/control/Orientation.lua @@ -0,0 +1,303 @@ +-- Copyright 2012 by Till Tantau +-- +-- This file may be distributed an/or modified +-- +-- 1. under the LaTeX Project Public License and/or +-- 2. under the GNU Public License +-- +-- See the file doc/generic/pgf/licenses/LICENSE for more information + +-- @release $Header$ + + + + +--- +-- @section subsection {Orienting a Graph} +-- +-- \label{subsection-library-graphdrawing-standard-orientation} +-- +-- Just as a graph drawing algorithm cannot know \emph{where} a graph +-- should be placed on a page, it is also often unclear which +-- \emph{orientation} it should have. Some graphs, like trees, have a +-- natural direction in which they ``grow'', but for an ``arbitrary'' +-- graph the ``natural orientation'' is, well, arbitrary. +-- +-- There are two ways in which you can specify an orientation: First, +-- you can specify that the line from a certain vertex to another +-- vertex should have a certain slope. How these vertices and slopes +-- are specified in explained momentarily. Second, you can specify a +-- so-called ``growth direction'' for trees. +-- +-- @end + + +-- Namespace +require("pgf.gd.control").Orientation = Orientation + + + +-- Imports +local declare = require "pgf.gd.interface.InterfaceToAlgorithms".declare + + +--- + +declare { + key = "orient", + type = "direction", + default = 0, + + summary = [[" + This key specifies that the straight line from the |orient tail| to + the |orient head| should be at an angle of \meta{direction} relative to + the right-going $x$-axis. Which vertices are used as tail an head + depends on where the |orient| option is encountered: When used with + an edge, the tail is the edge's tail and the head is the edge's + head. When used with a node, the tail or the head must be specified + explicitly and the node is used as the node missing in the + specification. When used with a graph as a whole, both the head and + tail nodes must be specified explicitly. + "]], + documentation = [[" + Note that the \meta{direction} is independent of the actual to-path + of an edge, which might define a bend or more complicated shapes. For + instance, a \meta{angle} of |45| requests that the end node is ``up + and right'' relative to the start node. + + You can also specify the standard direction texts |north| or |south east| + and so forth as \meta{direction} and also |up|, |down|, |left|, and + |right|. Also, you can specify |-| for ``right'' and \verb!|! for ``down''. + "]], + examples = {[[" + \tikz \graph [spring layout] + { + a -- { b, c, d, e -- {f, g, h} }; + h -- [orient=30] a; + }; + "]],[[" + \tikz \graph [spring layout] + { + a -- { b, c, d[> orient=right], e -- {f, g, h} }; + h -- a; + }; + "]] + } +} + + +--- + +declare { + key = "orient'", + type = "direction", + default = 0, + + summary = [[" + Same as |orient|, only the rest of the graph should be + flipped relative to the connection line. + "]], + examples = [[" + \tikz \graph [spring layout] + { + a -- { b, c, d[> orient'=right], e -- {f, g, h} }; + h -- a; + }; + "]] +} + +--- + +declare { + key = "orient tail", + type = "string", + + summary = [[" + Specifies the tail vertex for the orientation of a graph. See + |orient| for details. + "]], + examples = {[[" + \tikz \graph [spring layout] { + a [orient=|, orient tail=f] -- { b, c, d, e -- {f, g, h} }; + { h, g } -- a; + }; + "]],[[" + \tikz \graph [spring layout] { + a [orient=down, orient tail=h] -- { b, c, d, e -- {f, g, h} }; + { h, g } -- a; + }; + "]] + } +} + + + + + +--- + +declare { + key = "orient head", + type = "string", + + summary = [[" + Specifies the head vertex for the orientation of a graph. See + |orient| for details. + "]], + examples = {[[" + \tikz \graph [spring layout] + { + a [orient=|, orient head=f] -- { b, c, d, e -- {f, g, h} }; + { h, g } -- a; + }; + "]],[[" + \tikz \graph [spring layout] { a -- b -- c -- a }; + \tikz \graph [spring layout, orient=10, + orient tail=a, orient head=b] { a -- b -- c -- a }; + "]] + } +} + +--- + +declare { + key = "horizontal", + type = "string", + + summary = [[" + A shorthand for specifying |orient tail|, |orient head| and + |orient=0|. The tail will be everything before the part ``| to |'' + and the head will be everything following it. + "]], + examples = [[" + \tikz \graph [spring layout] { a -- b -- c -- a }; + \tikz \graph [spring layout, horizontal=a to b] { a -- b -- c -- a }; + "]] +} + + + + +--- + +declare { + key = "horizontal'", + type = "string", + + summary = [[" + Like |horizontal|, but with a flip. + "]] +} + + + + + + + +--- + +declare { + key = "vertical", + type = "string", + + summary = [[" + A shorthand for specifying |orient tail|, |orient head| and |orient=-90|. + "]], + examples = [[" + \tikz \graph [spring layout] { a -- b -- c -- a }; + \tikz \graph [spring layout, vertical=a to b] { a -- b -- c -- a }; + "]] +} + + + + + +--- + +declare { + key = "vertical'", + type = "string", + + summary = [[" + Like |vertical|, but with a flip. + "]] +} + + + +--- + +declare { + key = "grow", + type = "direction", + + summary = [[" + This key specifies in which direction the neighbors of a node + ``should grow''. For some graph drawing algorithms, especially for + those that layout trees, but also for those that produce layered + layouts, there is a natural direction in which the ``children'' of + a node should be placed. For instance, saying |grow=down| will cause + the children of a node in a tree to be placed in a left-to-right + line below the node (as always, you can replace the \meta{angle} + by direction texts). The children are requested to be placed in a + counter-clockwise fashion, the |grow'| key will place them in a + clockwise fashion. + "]], + documentation = [[" + Note that when you say |grow=down|, it is not necessarily the case + that any particular node is actually directly below the current + node; the key just requests that the direction of growth is downward. + + In principle, you can specify the direction of growth for each node + individually, but do not count on graph drawing algorithms to + honor these wishes. + + When you give the |grow=right| key to the graph as a whole, it will + be applied to all nodes. This happens to be exactly what you want: + "]], + examples = {[[" + \tikz \graph [layered layout, sibling distance=5mm] + { + a [grow=right] -- { b, c, d, e -- {f, g, h} }; + { h, g } -- a; + }; + "]],[[" + \tikz \graph [layered layout, grow=right, sibling distance=5mm] + { + a -- { b, c, d, e -- {f, g, h} }; + { h, g } -- a; + }; + "]],[[" + \tikz + \graph [layered layout, grow=-80] + { + {a,b,c} --[complete bipartite] {e,d,f} + --[complete bipartite] {g,h,i}; + }; + "]] + } +} + + +--- + +declare { + key = "grow'", + type = "direction", + + summary = "Same as |grow|, only with the children in clockwise order.", + examples = [[" + \tikz \graph [layered layout, sibling distance=5mm] + { + a [grow'=right] -- { b, c, d, e -- {f, g, h} }; + { h, g } -- a; + }; + "]] +} + + +-- Done + +return Orientation
\ No newline at end of file diff --git a/graphics/pgf/base/tex/generic/graphdrawing/lua/pgf/gd/control/Sublayouts.lua b/graphics/pgf/base/tex/generic/graphdrawing/lua/pgf/gd/control/Sublayouts.lua new file mode 100644 index 0000000000..970483193f --- /dev/null +++ b/graphics/pgf/base/tex/generic/graphdrawing/lua/pgf/gd/control/Sublayouts.lua @@ -0,0 +1,536 @@ +-- Copyright 2012 by Till Tantau +-- +-- This file may be distributed an/or modified +-- +-- 1. under the LaTeX Project Public License and/or +-- 2. under the GNU Public License +-- +-- See the file doc/generic/pgf/licenses/LICENSE for more information + +-- @release $Header$ + +local function full_print(g, pref) + local s = "" + + for _,v in ipairs(g.vertices) do + s = s .. tostring(v) .. "[" .. tostring(v.pos) .. "]\n " + end + + s = s .. "\n" + + for _,a in ipairs(g.arcs) do + for _,e in ipairs(a.syntactic_edges) do + s = s .. tostring(e) .. "(" .. tostring(e.path) .. ")\n" + end + end + + pgf.debug((pref or "") .. s) +end + + +--- +-- The |Sublayouts| module handles graphs for which multiple layouts are defined. +-- +-- Please see Section~\ref{section-gd-sublayouts} for an overview of +-- sublayouts. +-- + +local Sublayouts = {} + +-- Namespace +require("pgf.gd.control").Sublayouts = Sublayouts + + +-- Includes + +local Digraph = require "pgf.gd.model.Digraph" +local Vertex = require "pgf.gd.model.Vertex" +local Coordinate = require "pgf.gd.model.Coordinate" +local Path = require "pgf.gd.model.Path" + +local lib = require "pgf.gd.lib" + +local InterfaceCore = require "pgf.gd.interface.InterfaceCore" + +local Storage = require "pgf.gd.lib.Storage" + + + +-- Storages + +local subs = Storage.newTableStorage() +local already_nudged = Storage.new() +local positions = Storage.newTableStorage() + + + + +-- Offset a node by an offset. This will \emph{also} offset all +-- subnodes, which arise from sublayouts. +-- +-- @param vertex A vertex +-- @param pos A offset +-- +local function offset_vertex(v, delta) + v.pos:shiftByCoordinate(delta) + for _,sub in ipairs(subs[v]) do + offset_vertex(sub, delta) + end +end + + +-- Nudge positioning. You can call this function several times on the +-- same graph; nudging will be done only once. +-- +-- @param graph A graph +-- +local function nudge(graph) + for _,v in ipairs(graph.vertices) do + local nudge = v.options['nudge'] + if nudge and not already_nudged[v] then + offset_vertex(v, nudge) + already_nudged[v] = true + end + end +end + + + +-- Create subgraph nodes +-- +-- @param scope A scope +-- @param syntactic_digraph The syntactic digraph. +-- @param test Only for vertices whose subgraph collection passes this test will we create subgraph nodes +local function create_subgraph_node(scope, syntactic_digraph, vertex) + + local subgraph_collection = vertex.subgraph_collection + local binding = InterfaceCore.binding + + local cloud = {} + -- Add all points of n's collection, except for v itself, to the cloud: + for _,v in ipairs(subgraph_collection.vertices) do + if vertex ~= v then + assert(syntactic_digraph:contains(v), "the layout must contain all nodes of the subgraph") + for _,p in ipairs(v.path) do + if type(p) == "table" then + cloud[#cloud+1] = p + v.pos + end + end + end + end + for _,e in ipairs(subgraph_collection.edges) do + for _,p in ipairs(e.path) do + if type(p) == "table" then + cloud[#cloud+1] = p:clone() + end + end + end + local x_min, y_min, x_max, y_max, c_x, c_y = Coordinate.boundingBox(cloud) + + -- Shift the graph so that it is centered on the origin: + for _,p in ipairs(cloud) do + p:unshift(c_x,c_y) + end + + local o = vertex.subgraph_info.generated_options + + o[#o+1] = { key = "subgraph point cloud", value = table.concat(lib.imap(cloud, tostring)) } + o[#o+1] = { key = "subgraph bounding box height", value = tostring(y_max-y_min) .. "pt" } + o[#o+1] = { key = "subgraph bounding box width", value = tostring(x_max-x_min) .. "pt" } + + -- And now, the "grand call": + binding:createVertex(vertex.subgraph_info) + + -- Shift it were it belongs + vertex.pos:shift(c_x,c_y) + + -- Remember all the subnodes for nudging and regardless + -- positioning + local s = {} + for _,v in ipairs(subgraph_collection.vertices) do + if v ~= vertex then + s[#s+1] = v + end + end + + subs[vertex] = s +end + + +-- Tests whether two graphs have a vertex in common +local function intersection(g1, g2) + for _,v in ipairs(g1.vertices) do + if g2:contains(v) then + return v + end + end +end + +-- Tests whether a graph is a set is a subset of another +local function special_vertex_subset(vertices, graph) + for _,v in ipairs(vertices) do + if not graph:contains(v) and not (v.kind == "subgraph node") then + return false + end + end + return true +end + + + +--- +-- The layout recursion. See \ref{section-gd-sublayouts} for details. +-- +-- @param scope The graph drawing scope +-- @param layout The to-be-laid-out collection +-- @param fun The to-be-called function for laying out the graph. +-- processed. This stack is important when a new syntactic vertex is +-- added by the algorithm: In this case, this vertex is added to all +-- layouts on this stack. +-- +-- @return A laid out graph. + +function Sublayouts.layoutRecursively(scope, layout, fun) + + -- Step 1: Iterate over all sublayouts of the current layout: + local resulting_graphs = {} + local loc = Storage.new() + + -- Now, iterate over all sublayouts + for i,child in ipairs(layout:childrenOfKind(InterfaceCore.sublayout_kind)) do + resulting_graphs[i] = Sublayouts.layoutRecursively(scope, child, fun) + loc[resulting_graphs[i]] = child + end + + -- Step 2: Run the merge process: + local merged_graphs = {} + + while #resulting_graphs > 0 do + + local n = #resulting_graphs + + -- Setup marked array: + local marked = {} + for i=1,n do + marked[i] = false + end + + -- Mark first graph and copy everything from there + marked[1] = true + local touched = Storage.new() + for _,v in ipairs(resulting_graphs[1].vertices) do + v.pos = positions[v][resulting_graphs[1]] + touched[v] = true + end + + -- Repeatedly find a node that is connected to a marked node: + local i = 1 + while i <= n do + if not marked[i] then + for j=1,n do + if marked[j] then + local v = intersection(resulting_graphs[i], resulting_graphs[j]) + if v then + -- Aha, they intersect at vertex v + + -- Mark the i-th graph: + marked[i] = true + connected_some_graph = true + + -- Shift the i-th graph: + local x_offset = v.pos.x - positions[v][resulting_graphs[i]].x + local y_offset = v.pos.y - positions[v][resulting_graphs[i]].y + + for _,u in ipairs(resulting_graphs[i].vertices) do + if not touched[u] then + touched[u] = true + u.pos = positions[u][resulting_graphs[i]]:clone() + u.pos:shift(x_offset, y_offset) + + for _,a in ipairs(resulting_graphs[i]:outgoing(u)) do + for _,e in ipairs(a.syntactic_edges) do + for _,p in ipairs(e.path) do + if type(p) == "table" then + p:shift(x_offset, y_offset) + end + end + end + end + end + end + + -- Restart + i = 0 + break + end + end + end + end + i = i + 1 + end + + -- Now, we can collapse all marked graphs into one graph: + local merge = Digraph.new {} + merge.syntactic_digraph = merge + local remaining = {} + + -- Add all vertices and edges: + for i=1,n do + if marked[i] then + merge:add (resulting_graphs[i].vertices) + for _,a in ipairs(resulting_graphs[i].arcs) do + local ma = merge:connect(a.tail,a.head) + for _,e in ipairs(a.syntactic_edges) do + ma.syntactic_edges[#ma.syntactic_edges+1] = e + end + end + else + remaining[#remaining + 1] = resulting_graphs[i] + end + end + + -- Remember the first layout this came from: + loc[merge] = loc[resulting_graphs[1]] + + -- Restart with rest: + merged_graphs[#merged_graphs+1] = merge + + resulting_graphs = remaining + end + + -- Step 3: Run the algorithm on the layout: + + local class = layout.options.algorithm_phases.main + assert (type(class) == "table", "algorithm selection failed") + + local algorithm = class + local uncollapsed_subgraph_nodes = lib.imap( + scope.collections[InterfaceCore.subgraph_node_kind] or {}, + function (c) + if c.parent_layout == layout then + return c.subgraph_node + end + end) + + + -- Create a new syntactic digraph: + local syntactic_digraph = Digraph.new { + options = layout.options + } + + syntactic_digraph.syntactic_digraph = syntactic_digraph + + -- Copy all vertices and edges from the collection... + syntactic_digraph:add (layout.vertices) + for _,e in ipairs(layout.edges) do + syntactic_digraph:add {e.head, e.tail} + local arc = syntactic_digraph:connect(e.tail, e.head) + arc.syntactic_edges[#arc.syntactic_edges+1] = e + end + + -- Find out which subgraph nodes can be created now and make them part of the merged graphs + for i=#uncollapsed_subgraph_nodes,1,-1 do + local v = uncollapsed_subgraph_nodes[i] + local vertices = v.subgraph_collection.vertices + -- Test, if all vertices of the subgraph are in one of the merged graphs. + for _,g in ipairs(merged_graphs) do + if special_vertex_subset(vertices, g) then + -- Ok, we can create a subgraph now + create_subgraph_node(scope, syntactic_digraph, v) + -- Make it part of the collapse! + g:add{v} + -- Do not consider again + uncollapsed_subgraph_nodes[i] = false + break + end + end + end + + -- Collapse the nodes that are part of a merged_graph + local collapsed_vertices = {} + for _,g in ipairs(merged_graphs) do + + local intersection = {} + for _,v in ipairs(g.vertices) do + if syntactic_digraph:contains(v) then + intersection[#intersection+1] = v + end + end + if #intersection > 0 then + -- Compute bounding box of g (this should actually be the convex + -- hull) Hmm...: + local array = {} + for _,v in ipairs(g.vertices) do + local min_x, min_y, max_x, max_y = v:boundingBox() + array[#array+1] = Coordinate.new(min_x + v.pos.x, min_y + v.pos.y) + array[#array+1] = Coordinate.new(max_x + v.pos.x, max_y + v.pos.y) + end + for _,a in ipairs(g.arcs) do + for _,e in ipairs(a.syntactic_edges) do + for _,p in ipairs(e.path) do + if type(p) == "table" then + array[#array+1] = p + end + end + end + end + local x_min, y_min, x_max, y_max, c_x, c_y = Coordinate.boundingBox(array) + + -- Shift the graph so that it is centered on the origin: + for _,v in ipairs(g.vertices) do + v.pos:unshift(c_x,c_y) + end + for _,a in ipairs(g.arcs) do + for _,e in ipairs(a.syntactic_edges) do + for _,p in ipairs(e.path) do + if type(p) == "table" then + p:unshift(c_x,c_y) + end + end + end + end + + x_min = x_min - c_x + x_max = x_max - c_x + y_min = y_min - c_y + y_max = y_max - c_y + + local index = loc[g].event.index + + local v = Vertex.new { + -- Standard stuff + shape = "none", + kind = "node", + path = Path.new { + "moveto", + x_min, y_min, + x_min, y_max, + x_max, y_max, + x_max, y_min, + "closepath" + }, + options = {}, + event = scope.events[index] + } + + -- Update node_event + scope.events[index].parameters = v + + local collapse_vertex = syntactic_digraph:collapse( + intersection, + v, + nil, + function (new_arc, arc) + for _,e in ipairs(arc.syntactic_edges) do + new_arc.syntactic_edges[#new_arc.syntactic_edges+1] = e + end + end) + + syntactic_digraph:remove(intersection) + collapsed_vertices[#collapsed_vertices+1] = collapse_vertex + end + end + + -- Sort the vertices + table.sort(syntactic_digraph.vertices, function(u,v) return u.event.index < v.event.index end) + + -- Should we "hide" the subgraph nodes? + local hidden_node + if not algorithm.include_subgraph_nodes then + local subgraph_nodes = lib.imap (syntactic_digraph.vertices, + function (v) if v.kind == "subgraph node" then return v end end) + + if #subgraph_nodes > 0 then + hidden_node = Vertex.new {} + syntactic_digraph:collapse(subgraph_nodes, hidden_node) + syntactic_digraph:remove (subgraph_nodes) + syntactic_digraph:remove {hidden_node} + end + end + + -- Now, we want to call the actual algorithm. This call may modify + -- the layout's vertices and edges fields, namely when new vertices + -- and edges are created. We then need to add these to our local + -- syntactic digraph. So, we remember the length of these fields + -- prior to the call and then add everything ``behind'' these + -- positions later on. + + -- Ok, everything setup! Run the algorithm... + fun(scope, algorithm, syntactic_digraph, layout) + + if hidden_node then + syntactic_digraph:expand(hidden_node) + end + + -- Now, we need to expand the collapsed vertices once more: + for i=#collapsed_vertices,1,-1 do + syntactic_digraph:expand( + collapsed_vertices[i], + function (c, v) + v.pos:shiftByCoordinate(c.pos) + end, + function (a, v) + for _,e in ipairs(a.syntactic_edges) do + for _,p in ipairs(e.path) do + if type(p) == "table" then + p:shiftByCoordinate(v.pos) + end + end + end + end + ) + for _,a in ipairs(syntactic_digraph:outgoing(collapsed_vertices[i])) do + for _,e in ipairs(a.syntactic_edges) do + for _,p in ipairs(e.path) do + if type(p) == "table" then + p:shiftByCoordinate(a.tail.pos) + p:unshiftByCoordinate(e.tail.pos) + end + end + end + end + end + syntactic_digraph:remove(collapsed_vertices) + + -- Step 4: Create the layout node if necessary + for i=#uncollapsed_subgraph_nodes,1,-1 do + if uncollapsed_subgraph_nodes[i] then + create_subgraph_node(scope, syntactic_digraph, uncollapsed_subgraph_nodes[i]) + end + end + + -- Now seems like a good time to nudge and do regardless positioning + nudge(syntactic_digraph) + + -- Step 5: Cleanup + -- Push the computed position into the storage: + for _,v in ipairs(syntactic_digraph.vertices) do + positions[v][syntactic_digraph] = v.pos:clone() + end + + return syntactic_digraph +end + + + + + +--- +-- Regardless positioning. +-- +-- @param graph A graph +-- +function Sublayouts.regardless(graph) + for _,v in ipairs(graph.vertices) do + local regardless = v.options['regardless at'] + if regardless then + offset_vertex(v, regardless - v.pos) + end + end +end + + + +-- Done + +return Sublayouts diff --git a/graphics/pgf/base/tex/generic/graphdrawing/lua/pgf/gd/control/doc.lua b/graphics/pgf/base/tex/generic/graphdrawing/lua/pgf/gd/control/doc.lua new file mode 100644 index 0000000000..19bfd52fdf --- /dev/null +++ b/graphics/pgf/base/tex/generic/graphdrawing/lua/pgf/gd/control/doc.lua @@ -0,0 +1,245 @@ +-- Copyright 2012 by Till Tantau +-- +-- This file may be distributed an/or modified +-- +-- 1. under the LaTeX Project Public License and/or +-- 2. under the GNU Public License +-- +-- See the file doc/generic/pgf/licenses/LICENSE for more information + +-- @release $Header$ + +local key = require 'pgf.gd.doc'.key +local documentation = require 'pgf.gd.doc'.documentation +local summary = require 'pgf.gd.doc'.summary +local example = require 'pgf.gd.doc'.example + + +-------------------------------------------------------------------- +key "desired at" + +summary +[[ +When you add this key to a node in a graph, you ``desire'' +that the node should be placed at the \meta{coordinate} by the graph +drawing algorithm. +]] + +documentation +[[ +Now, when you set this key for a single node of a graph, +then, by shifting the graph around, this ``wish'' can obviously +always be fulfill: +% +\begin{codeexample}[preamble={ \usetikzlibrary{graphs,graphdrawing} + \usegdlibrary{force}}] +\begin{tikzpicture} + \draw [help lines] (0,0) grid (3,2); + \graph [spring layout] + { + a [desired at={(1,2)}] -- b -- c -- a; + }; +\end{tikzpicture} +\end{codeexample} +% +\begin{codeexample}[preamble={ \usetikzlibrary{graphs,graphdrawing} + \usegdlibrary{force}}] +\begin{tikzpicture} + \draw [help lines] (0,0) grid (3,2); + \graph [spring layout] + { + a -- b[desired at={(2,1)}] -- c -- a; + }; +\end{tikzpicture} +\end{codeexample} +% +Since the key's name is a bit long and since the many braces and +parentheses are a bit cumbersome, there is a special support for +this key inside a |graph|: The standard |/tikz/at| key is redefined +inside a |graph| so that it points to |/graph drawing/desired at| +instead. (Which is more logical anyway, since it makes no sense to +specify an |at| position for a node whose position it to be computed +by a graph drawing algorithm.) A nice side effect of this is that +you can use the |x| and |y| keys (see +Section~\ref{section-graphs-xy}) to specify desired positions: +% +\begin{codeexample}[preamble={ \usetikzlibrary{graphs,graphdrawing} + \usegdlibrary{force}}] +\begin{tikzpicture} + \draw [help lines] (0,0) grid (3,2); + \graph [spring layout] + { + a -- b[x=2,y=0] -- c -- a; + }; +\end{tikzpicture} +\end{codeexample} +% +\begin{codeexample}[preamble={ \usetikzlibrary{graphs,graphdrawing} + \usegdlibrary{layered}}] +\begin{tikzpicture} + \draw [help lines] (0,0) grid (3,2); + \graph [layered layout] + { + a [x=1,y=2] -- { b, c } -- {e, f} -- a + }; +\end{tikzpicture} +\end{codeexample} + +A problem arises when two or more nodes have this key set, because +then your ``desires'' for placement and the positions computed by +the graph drawing algorithm may clash. Graph drawing algorithms are +``told'' about the desired positions. Most algorithms will simply +ignore these desired positions since they will be taken care of in +the so-called post-anchoring phase, see below. However, for some +algorithms it makes a lot of sense to fix the positions of some +nodes and only compute the positions of the other nodes relative +to these nodes. For instance, for a |spring layout| it makes perfect +sense that some nodes are ``nailed to the canvas'' while other +nodes can ``move freely''. +]] + +--[[ +% TODOsp: codeexamples: the following 3 examples need these libraries +% \usetikzlibrary{graphs,graphdrawing} +% \usegdlibrary{force} +--]] +example +[[ +\begin{tikzpicture} + \draw [help lines] (0,0) grid (3,2); + \graph [spring layout] + { + a[x=1] -- { b, c, d, e -- {f,g,h} }; + { h, g } -- a; + }; +\end{tikzpicture} +]] + +example +[[ +\begin{tikzpicture} + \draw [help lines] (0,0) grid (3,2); + \graph [spring layout] + { + a -- { b, c, d[x=0], e -- {f[x=2], g, h[x=1]} }; + { h, g } -- a; + }; +\end{tikzpicture} +]] + +example +[[ +\begin{tikzpicture} + \draw [help lines] (0,0) grid (3,2); + \graph [spring layout] + { + a -- { b, c, d[x=0], e -- {f[x=2,y=1], g, h[x=1]} }; + { h, g } -- a; + }; +\end{tikzpicture} +]] +-------------------------------------------------------------------- + + + +-------------------------------------------------------------------- +key "anchor node" + +summary +[[ +This option can be used with a graph to specify a node that +should be used for anchoring the whole graph. +]] + +documentation +[[ +When this option is specified, after the layout has been computed, the +whole graph will be shifted in such a way that the \meta{node name} is +either +% +\begin{itemize} + \item at the current value of |anchor at| or + \item at the position that is specified in the form of a + |desired at| for the \meta{node name}. +\end{itemize} +% +Note how in the last example |c| is placed at |(1,1)| rather than +|b| as would happen by default. +]] + +--[[ +% TODOsp: codeexamples: the following 4 examples need these libraries +% \usetikzlibrary{graphs,graphdrawing} +% \usegdlibrary{layered} +--]] +example +[[ +\tikz \draw (0,0) + -- (1,0.5) graph [edges=red, layered layout, anchor node=a] { a -> {b,c} } + -- (1.5,0) graph [edges=blue, layered layout, + anchor node=y, anchor at={(2,0)}] { x -> {y,z} }; +]] + +example +[[ +\begin{tikzpicture} + \draw [help lines] (0,0) grid (3,2); + + \graph [layered layout, anchor node=c, edges=rounded corners] + { a -- {b [x=1,y=1], c [x=1,y=1] } -- d -- a}; +\end{tikzpicture} +]] +-------------------------------------------------------------------- + + + + +-------------------------------------------------------------------- +key "anchor at" + +summary +[[ +The coordinate at which the graph should be anchored when no +explicit anchor is given for any node. The initial value is the origin. +]] + +example +[[ +\begin{tikzpicture} + \draw [help lines] (0,0) grid (2,2); + + \graph [layered layout, edges=rounded corners, anchor at={(1,2)}] + { a -- {b, c [anchor here] } -- d -- a}; +\end{tikzpicture} +]] +-------------------------------------------------------------------- + + + + +-------------------------------------------------------------------- +key "anchor here" + +summary +[[ +This option can be passed to a single node (rather than the +graph as a whole) in order to specify that this node should be used +for the anchoring process. +]] + +documentation +[[ +In the example, |c| is placed at the origin since this is the +default |anchor at| position. +]] + +example +[[ +\begin{tikzpicture} + \draw [help lines] (0,0) grid (2,2); + + \graph [layered layout, edges=rounded corners] + { a -- {b, c [anchor here] } -- d -- a}; +\end{tikzpicture} +]] +-------------------------------------------------------------------- diff --git a/graphics/pgf/base/tex/generic/graphdrawing/lua/pgf/gd/control/library.lua b/graphics/pgf/base/tex/generic/graphdrawing/lua/pgf/gd/control/library.lua new file mode 100644 index 0000000000..df7ce68dd9 --- /dev/null +++ b/graphics/pgf/base/tex/generic/graphdrawing/lua/pgf/gd/control/library.lua @@ -0,0 +1,196 @@ +-- Copyright 2012 by Till Tantau +-- +-- This file may be distributed an/or modified +-- +-- 1. under the LaTeX Project Public License and/or +-- 2. under the GNU Public License +-- +-- See the file doc/generic/pgf/licenses/LICENSE for more information + +-- @release $Header$ + + +-- Load declarations from: + +require "pgf.gd.control.FineTune" +require "pgf.gd.control.Anchoring" +require "pgf.gd.control.Sublayouts" +require "pgf.gd.control.Orientation" +require "pgf.gd.control.Distances" +require "pgf.gd.control.Components" +require "pgf.gd.control.ComponentAlign" +require "pgf.gd.control.ComponentDirection" +require "pgf.gd.control.ComponentDistance" +require "pgf.gd.control.ComponentOrder" +require "pgf.gd.control.NodeAnchors" + + +local InterfaceCore = require "pgf.gd.interface.InterfaceCore" +local declare = require "pgf.gd.interface.InterfaceToAlgorithms".declare +local lib = require "pgf.gd.lib" + + + +--- + +declare { + key = "nodes behind edges", + type = "boolean", + + summary = "Specifies, that nodes should be drawn behind the edges", + documentation = [[" + Once a graph drawing algorithm has determined positions for the nodes, + they are drawn \emph{before} the edges are drawn; after + all, it is hard to draw an edge between nodes when their positions + are not yet known. However, we typically want the nodes to be + rendered \emph{after} or rather \emph{on top} of the edges. For + this reason, the default behavior is that the nodes at their + final positions are collected in a box that is inserted into the + output stream only after the edges have been drawn -- which has + the effect that the nodes will be placed ``on top'' of the + edges. + + This behavior can be changed using this option. When the key is + invoked, nodes are placed \emph{behind} the edges. + "]], + examples = [[" + \tikz \graph [simple necklace layout, nodes={draw,fill=white}, + nodes behind edges] + { subgraph K_n [n=7], 1 [regardless at={(0,-1)}] }; + "]] +} + + +--- + +declare { + key = "edges behind nodes", + use = { + { key = "nodes behind edges", value = "false" }, + }, + + summary = [[" + This is the default placement of edges: Behind the nodes. + "]], + examples = [[" + \tikz \graph [simple necklace layout, nodes={draw,fill=white}, + edges behind nodes] + { subgraph K_n [n=7], 1 [regardless at={(0,-1)}] }; + "]] +} + +--- +declare { + key = "random seed", + type = "number", + initial = "42", + + summary = [[" + To ensure that the same is always shown in the same way when the + same algorithm is applied, the random is seed is reset on each call + of the graph drawing engine. To (possibly) get different results on + different runs, change this value. + "]] +} + + +--- +declare { + key = "variation", + type = "number", + use = { + { key = "random seed", value = lib.id }, + }, + summary = "An alias for |random seed|." +} + + +--- +declare { + key = "weight", + type = "number", + initial = 1, + + summary = [[" + Sets the ``weight'' of an edge or a node. For many algorithms, this + number tells the algorithm how ``important'' the edge or node is. + For instance, in a |layered layout|, an edge with a large |weight| + will be as short as possible. + "]], + examples = {[[" + \tikz \graph [layered layout] { + a -- {b,c,d} -- e -- a; + }; + "]],[[" + \tikz \graph [layered layout] { + a -- {b,c,d} -- e --[weight=3] a; + }; + "]] + } +} + + + +--- +declare { + key = "length", + type = "length", + initial = 1, + + summary = [[" + Sets the ``length'' of an edge. Algorithms may take this value + into account when drawing a graph. + "]], + examples = {[[" + \tikz \graph [phylogenetic tree layout] { + a --[length=2] b --[length=1] {c,d}; + a --[length=3] e + }; + "]], + } +} + + +--- + +declare { + key = "radius", + type = "number", + initial = "0", + + summary = [[" + The radius of a circular object used in graph drawing. + "]] +} + +--- + +declare { + key = "no layout", + algorithm = { + run = + function (self) + for _,v in ipairs(self.digraph.vertices) do + if v.options['desired at'] then + v.pos.x = v.options['desired at'].x + v.pos.y = v.options['desired at'].y + end + end + end }, + summary = "This layout does nothing.", +} + + + +-- The following collection kinds are internal + +declare { + key = InterfaceCore.sublayout_kind, + layer = 0 +} + +declare { + key = InterfaceCore.subgraph_node_kind, + layer = 0 +} + diff --git a/graphics/pgf/base/tex/generic/graphdrawing/lua/pgf/gd/deprecated/Cluster.lua b/graphics/pgf/base/tex/generic/graphdrawing/lua/pgf/gd/deprecated/Cluster.lua new file mode 100644 index 0000000000..d7baed45da --- /dev/null +++ b/graphics/pgf/base/tex/generic/graphdrawing/lua/pgf/gd/deprecated/Cluster.lua @@ -0,0 +1,65 @@ +-- Copyright 2011 by Jannis Pohlmann +-- Copyright 2012 by Till Tantau +-- +-- This file may be distributed an/or modified +-- +-- 1. under the LaTeX Project Public License and/or +-- 2. under the GNU Public License +-- +-- See the file doc/generic/pgf/licenses/LICENSE for more information + +-- @release $Header$ + + + +--- The Cluster class defines a model of a cluster inside a graph. +-- +-- + +local Cluster = {} +Cluster.__index = Cluster + + +-- Namespace + + + +--- TODO Jannis: Add documentation for this class. +-- +function Cluster.new(name) + local cluster = { + name = name, + nodes = {}, + contains_node = {}, + } + setmetatable(cluster, Cluster) + return cluster +end + + + +function Cluster:getName() + return self.name +end + + + +function Cluster:addNode(node) + if not self:findNode(node) then + self.contains_node[node] = true + self.nodes[#self.nodes + 1] = node + end +end + + + +function Cluster:findNode(node) + return self.contains_node[node] +end + + + + +-- Done + +return Cluster
\ No newline at end of file diff --git a/graphics/pgf/base/tex/generic/graphdrawing/lua/pgf/gd/deprecated/Edge.lua b/graphics/pgf/base/tex/generic/graphdrawing/lua/pgf/gd/deprecated/Edge.lua new file mode 100644 index 0000000000..7355597955 --- /dev/null +++ b/graphics/pgf/base/tex/generic/graphdrawing/lua/pgf/gd/deprecated/Edge.lua @@ -0,0 +1,358 @@ +-- Copyright 2010 by Renée Ahrens, Olof Frahm, Jens Kluttig, Matthias Schulz, Stephan Schuster +-- Copyright 2011 by Jannis Pohlmann +-- Copyright 2012 by Till Tantau +-- +-- This file may be distributed an/or modified +-- +-- 1. under the LaTeX Project Public License and/or +-- 2. under the GNU Public License +-- +-- See the file doc/generic/pgf/licenses/LICENSE for more information + +-- @release $Header$ + + + + +--- The Edge class +-- +-- + +local Edge = {} +Edge.__index = Edge + + +-- Namespace + +local lib = require "pgf.gd.lib" + + +-- Definitions + +Edge.UNDIRECTED = "--" +Edge.LEFT = "<-" +Edge.RIGHT = "->" +Edge.BOTH = "<->" +Edge.NONE = "-!-" + + +--- Creates an edge between nodes of a graph. +-- +-- @param values Values to override default edge settings. +-- The following parameters can be set:\par +-- |nodes|: TODO \par +-- |edge_nodes|: TODO \par +-- |options|: TODO \par +-- |tikz_options|: TODO \par +-- |direction|: TODO \par +-- |bend_points|: TODO \par +-- |bend_nodes|: TODO \par +-- |reversed|: TODO \par +-- +-- @return A newly-allocated edge. +-- +function Edge.new(values) + local defaults = { + nodes = {}, + edge_nodes = '', + options = {}, + tikz_options = {}, + direction = Edge.DIRECTED, + bend_points = {}, + bend_nodes = {}, + reversed = false, + algorithmically_generated_options = {}, + index = nil, + event_index = nil, + } + setmetatable(defaults, Edge) + if values then + for k,v in pairs(values) do + defaults[k] = v + end + end + return defaults +end + + + +--- Sets the edge option \meta{name} to \meta{value}. +-- +-- @param name Name of the option to be changed. +-- @param value New value for the edge option \meta{name}. +-- +function Edge:setOption(name, value) + self.options[name] = value +end + + + +--- Returns the value of the edge option \meta{name}. +-- +-- @param name Name of the option. +-- @param graph If this optional argument is given, +-- in case the option is not set as a node parameter, +-- we try to look it up as a graph parameter. +-- +-- @return The value of the edge option \meta{name} or |nil|. +-- +function Edge:getOption(name, graph) + return lib.lookup_option(name, self, graph) +end + + + +--- Checks whether or not the edge is a loop edge. +-- +-- An edge is a loop if it one node multiple times and no other node. +-- +-- @return |true| if the edge is a loop, |false| otherwise. +-- +function Edge:isLoop() + local nodes = self.nodes + for i=1,#nodes do + if nodes[i] ~= nodes[1] then + return false + end + end + return true +end + + + +--- Returns whether or not the edge is a hyperedge. +-- +-- A hyperedge is an edge with more than two adjacent nodes. +-- +-- @return |true| if the edge is a hyperedge. |false| otherwise. +-- +function Edge:isHyperedge() + return self:getDegree() > 2 +end + + + +--- Returns all nodes of the edge. +-- +-- Instead of calling |edge:getNodes()| the nodes can alternatively be +-- accessed directly with |edge.nodes|. +-- +-- @return All edges of the node. +-- +function Edge:getNodes() + return self.nodes +end + + + +--- Returns whether or not a node is adjacent to the edge. +-- +-- @param node The node to check. +-- +-- @return |true| if the node is adjacent to the edge. |false| otherwise. +-- +function Edge:containsNode(node) + return lib.find(self.nodes, function (other) return other == node end) ~= nil +end + + + +--- If possible, adds a node to the edge. +-- +-- @param node The node to be added to the edge. +-- +function Edge:addNode(node) + table.insert(self.nodes, node) + node:addEdge(self) +end + + + +--- Gets first neighbor of the node (disregarding hyperedges). +-- +-- @param node The node which first neighbor should be returned. +-- +-- @return The first neighbor of the node. +-- +function Edge:getNeighbour(node) + if node == self.nodes[1] then + return self.nodes[#self.nodes] + else + return self.nodes[1] + end +end + + + +--- Counts the nodes on this edge. +-- +-- @return The number of nodes on the edge. +-- +function Edge:getDegree() + return #self.nodes +end + + + +function Edge:getHead() + -- by default, the head of -> edges is the last node and the head + -- of <- edges is the first node + local head_index = (self.direction == Edge.LEFT) and 1 or #self.nodes + + -- if the edge should be assumed reversed, we simply switch head and + -- tail positions + if self.reversed then + head_index = (head_index == 1) and #self.nodes or 1 + end + + return self.nodes[head_index] +end + + + +function Edge:getTail() + -- by default, the tail of -> edges is the first node and the tail + -- of <- edges is the last node + local tail_index = (self.direction == Edge.LEFT) and #self.nodes or 1 + + -- if the edge should be assumed reversed, we simply switch head + -- and tail positions + if self.reversed then + tail_index = (tail_index == 1) and #self.nodes or 1 + end + + return self.nodes[tail_index] +end + + + +--- Checks whether a node is the head of the edge. Does not work for hyperedges. +-- +-- This method only works for edges with two adjacent nodes. +-- +-- Edges may be reversed internally, so their head and tail might be switched. +-- Whether or not this internal reversal is handled by this method +-- can be specified with the optional second \meta{ignore\_reversed} parameter +-- which is |false| by default. +-- +-- @param node The node to check. +-- +-- @return True if the node is the head of the edge. +-- +function Edge:isHead(node) + local result = false + + -- by default, the head of -> edges is the last node and the head + -- of <- edges is the first node + local head_index = (self.direction == Edge.LEFT) and 1 or #self.nodes + + -- if the edge should be assumed reversed, we simply switch head and + -- tail positions + if self.reversed then + head_index = (head_index == 1) and #self.nodes or 1 + end + + -- check if the head node equals the input node + if self.nodes[head_index].name == node.name then + result = true + end + + return result +end + + + +--- Checks whether a node is the tail of the edge. Does not work for hyperedges. +-- +-- This method only works for edges with two adjacent nodes. +-- +-- Edges may be reversed internally, so their head and tail might be switched. +-- Whether or not this internal reversal is handled by this method +-- can be specified with the optional second \meta{ignore\_reversed} parameter +-- which is |false| by default. +-- +-- @param node The node to check. +-- @param ignore_reversed Optional parameter. Set this to true if reversed edges +-- should not be considered reversed for this method call. +-- +-- @return True if the node is the tail of the edge. +-- +function Edge:isTail(node, ignore_reversed) + local result = false + + -- by default, the tail of -> edges is the first node and the tail + -- of <- edges is the last node + local tail_index = (self.direction == Edge.LEFT) and #self.nodes or 1 + + -- if the edge should be assumed reversed, we simply switch head + -- and tail positions + if self.reversed then + tail_index = (tail_index == 1) and #self.nodes or 1 + end + + -- check if the tail node equals the input node + if self.nodes[tail_index].name == node.name then + result = true + end + + return result +end + + + +--- Copies an edge (preventing accidental use). +-- +-- The nodes of the edge are not preserved and have to be added +-- to the copy manually if necessary. +-- +-- @return Shallow copy of the edge. +-- +function Edge:copy() + local result = lib.copy(self, Edge.new()) + result.nodes = {} + return result + end + + + + +local function reverse_values(source) + local copy = {} + for i = 1,#source do + copy[i] = source[#source-i+1] + end + return copy +end + + +--- Returns a readable string representation of the edge. +-- +-- @ignore This should not appear in the documentation. +-- +-- @return String representation of the edge. +-- +function Edge:__tostring() + local result = "Edge(" .. self.direction .. ", reversed = " .. tostring(self.reversed) .. ", " + if #self.nodes > 0 then + local node_strings = lib.imap(self.nodes, function (node) return node.name end) + result = result .. table.concat(node_strings, ', ') + end + --return result .. ")" + + -- Note: the following lines generate a shorter string representation + -- of the edge that is more readable and can be used for debugging. + -- So please don't remove this: + -- + local node_strings = lib.imap(self.nodes, function (node) return node.name end) + if self.reversed then + return table.concat(reverse_values(node_strings), ' ' .. self.direction .. ' ') + else + return table.concat(node_strings, ' ' .. self.direction .. ' ') + end +end + + + +-- Done + +return Edge
\ No newline at end of file diff --git a/graphics/pgf/base/tex/generic/graphdrawing/lua/pgf/gd/deprecated/Graph.lua b/graphics/pgf/base/tex/generic/graphdrawing/lua/pgf/gd/deprecated/Graph.lua new file mode 100644 index 0000000000..161b89fd15 --- /dev/null +++ b/graphics/pgf/base/tex/generic/graphdrawing/lua/pgf/gd/deprecated/Graph.lua @@ -0,0 +1,396 @@ +-- Copyright 2010 by Renée Ahrens, Olof Frahm, Jens Kluttig, Matthias Schulz, Stephan Schuster +-- Copyright 2011 by Jannis Pohlmann +-- Copyright 2012 by Till Tantau +-- +-- This file may be distributed an/or modified +-- +-- 1. under the LaTeX Project Public License and/or +-- 2. under the GNU Public License +-- +-- See the file doc/generic/pgf/licenses/LICENSE for more information + +-- @release $Header$ + + + + +--- The Graph class +-- +-- + +local Graph = {} +Graph.__index = Graph + + +-- Namespace + +-- Imports +local Edge = require "pgf.gd.deprecated.Edge" + +local lib = require "pgf.gd.lib" + + +--- Creates a new graph. +-- +-- @param values Values to override default graph settings. +-- The following parameters can be set:\par +-- |nodes|: The nodes of the graph.\par +-- |edges|: The edges of the graph.\par +-- |clusters|: The node clusters of the graph.\par +-- |options|: A table of node options passed over from \tikzname. +-- |events|: A sequence of events signaled during the graph specification. +-- +-- @return A newly-allocated graph. +-- +function Graph.new(values) + local defaults = { + nodes = {}, + edges = {}, + clusters = {}, + options = {}, + events = {}, + } + setmetatable(defaults, Graph) + if values then + for k,v in pairs(values) do + defaults[k] = v + end + end + return defaults +end + + + +--- Prepares a graph for an algorithm. +-- +-- This method causes self, all its nodes, and all its edges to get +-- a new empty table for the key algorithm. This allows an algorithm to +-- store stuff with nodes and edges without them interfering with information +-- stored by other algorithms. +-- +-- @param An algorithm object. + +function Graph:registerAlgorithm(algorithm) + self[algorithm] = self[algorithm] or {} + + -- Add an algorithm field to all nodes, all edges, and the graph: + for _,n in pairs(self.nodes) do + n[algorithm] = n[algorithm] or {} + end + for _,e in pairs(self.edges) do + e[algorithm] = e[algorithm] or {} + end +end + + +--- Sets the graph option \meta{name} to \meta{value}. +-- +-- @param name Name of the option to be changed. +-- @param value New value for the graph option \meta{name}. +-- +function Graph:setOption(name, value) + self.options[name] = value +end + + + +--- Returns the value of the graph option \meta{name}. +-- +-- @param name Name of the option. +-- +-- @return The value of the graph option \meta{name} or |nil|. +-- +function Graph:getOption(name) + return self.options[name] +end + + + + +--- Creates a shallow copy of a graph. +-- +-- The nodes and edges of the original graph are not preserved in the copy. +-- +-- @return A shallow copy of the graph. +-- +function Graph:copy () + return Graph.new({options = self.options, events = self.events}) +end + + +--- Adds a node to the graph. +-- +-- @param node The node to be added. +-- +function Graph:addNode(node) + -- only add the node if it's not included in the graph yet + if not self:findNode(node.name) then + -- Does the node have an index, yet? + if not node.index then + node.index = #self.nodes + 1 + end + + table.insert(self.nodes, node) + end +end + + + +--- If possible, removes a node from the graph and returns it. +-- +-- @param node The node to remove. +-- +-- @return The removed node or |nil| if it was not found in the graph. +-- +function Graph:removeNode(node) + local _, index = lib.find(self.nodes, function (other) + return other.name == node.name + end) + if index then + table.remove(self.nodes, index) + return node + else + return nil + end +end + + + +--- If possible, looks up the node with the given name in the graph. +-- +-- @param name Name of the node to look up. +-- +-- @return The node with the given name or |nil| if it was not found in the graph. +-- +function Graph:findNode(name) + return self:findNodeIf(function (node) return node.name == name end) +end + + + +--- Looks up the first node for which the function \meta{test} returns |true|. +-- +-- @param test A function that takes one parameter (a |Node|) and returns +-- |true| or |false|. +-- +-- @return The first node for which \meta{test} returns |true|. +-- +function Graph:findNodeIf(test) + return lib.find(self.nodes, test) +end + + + +--- Like removeNode, but also deletes all adjacent edges of the removed node. +-- +-- This function also removes the deleted adjacent edges from all neighbors +-- of the removed node. +-- +-- @param node The node to be deleted together with its adjacent edges. +-- +-- @return The removed node or |nil| if the node was not found in the graph. +-- +function Graph:deleteNode(node) + local node = self:removeNode(node) + if node then + for _,edge in ipairs(node.edges) do + self:removeEdge(edge) + for _,other_node in ipairs(edge.nodes) do + if other_node.name ~= node.name then + other_node:removeEdge(edge) + end + end + end + node.edges = {} + end + return node +end + + + +-- Checks whether the edge already exists in the graph and returns it if possible. +-- +-- @param edge Edge to search for. +-- +-- @return The edge if it was found in the graph, |nil| otherwise. +-- +function Graph:findEdge(edge) + return lib.find(self.edges, function (other) return other == edge end) +end + + + +--- Adds an edge to the graph. +-- +-- @param edge The edge to be added. +-- +function Graph:addEdge(edge) + if not edge.index then + edge.index = #self.edges + 1 + end + + table.insert(self.edges, edge) +end + + + +--- If possible, removes an edge from the graph and returns it. +-- +-- @param edge The edge to be removed. +-- +-- @return The removed edge or |nil| if it was not found in the graph. +-- +function Graph:removeEdge(edge) + local _, index = lib.find(self.edges, function (other) return other == edge end) + if index then + table.remove(self.edges, index) + return edge + else + return nil + end +end + + + +--- Like removeEdge, but also removes the edge from its adjacent nodes. +-- +-- @param edge The edge to be deleted. +-- +-- @return The removed edge or |nil| if it was not found in the graph. +-- +function Graph:deleteEdge(edge) + local edge = self:removeEdge(edge) + if edge then + for _,node in ipairs(edge.nodes) do + node:removeEdge(edge) + end + end + return edge +end + + + +--- Removes an edge between two nodes and also removes it from these nodes. +-- +-- @param from Start node of the edge. +-- @param to End node of the edge. +-- +-- @return The deleted edge. +-- +function Graph:deleteEdgeBetweenNodes(from, to) + -- try to find the edge + local edge = lib.find(self.edges, function (edge) + return edge.nodes[1] == from and edge.nodes[2] == to + end) + + -- delete and return the edge + if edge then + return self:deleteEdge(edge) + else + return nil + end +end + + + +--- Creates and adds a new edge to the graph. +-- +-- @param first_node The first node of the new edge. +-- @param second_node The second node of the new edge. +-- @param direction The direction of the new edge. Possible values are +-- \begin{itemize} +-- \item |Edge.UNDIRECTED|, +-- \item |Edge.LEFT|, +-- \item |Edge.RIGHT|, +-- \item |Edge.BOTH| and +-- \item |Edge.NONE| (for invisible edges). +-- \end{itemize} +-- @param edge_nodes A string of \tikzname\ edge nodes that needs to be passed +-- back to the \TeX layer unmodified. +-- @param options The options of the new edge. +-- @param tikz_options A table of \tikzname\ options to be used by graph drawing +-- algorithms to treat the edge in special ways. +-- +-- @return The newly created edge. +-- +function Graph:createEdge(first_node, second_node, direction, edge_nodes, options, tikz_options) + local edge = Edge.new{ + direction = direction, + edge_nodes = edge_nodes, + options = options, + tikz_options = tikz_options + } + edge:addNode(first_node) + edge:addNode(second_node) + self:addEdge(edge) + return edge +end + + + +--- Returns the cluster with the given name or |nil| if no such cluster exists. +-- +-- @param name Name of the node cluster to look up. +-- +-- @return The cluster with the given name or |nil| if no such cluster is defined. +-- +function Graph:findClusterByName(name) + return lib.find(self.clusters, function (cluster) + return cluster.name == name + end) +end + + + +--- Tries to add a cluster to the graph. Returns whether or not this was successful. +-- +-- Clusters are supposed to have unique names. This function will add the given +-- cluster only if there is no cluster with this name already. It returns |true| +-- if the cluster was added and |false| otherwise. +-- +-- @param cluster Cluster to add to the graph. +-- +-- @return |true| if the cluster was added successfully, |false| otherwise. +-- +function Graph:addCluster(cluster) + if not self:findClusterByName(cluster.name) then + table.insert(self.clusters, cluster) + end +end + + + + + +--- Returns a string representation of this graph including all nodes and edges. +-- +-- @ignore This should not appear in the documentation. +-- +-- @return Graph as string. +-- +function Graph:__tostring() + local tmp = Graph.__tostring + Graph.__tostring = nil + local result = "Graph<" .. tostring(self) .. ">((" + Graph.__tostring = tmp + + local first = true + for _,node in ipairs(self.nodes) do + if first then first = false else result = result .. ", " end + result = result .. tostring(node) + end + result = result .. "), (" + first = true + for _,edge in ipairs(self.edges) do + if first then first = false else result = result .. ", " end + result = result .. tostring(edge) + end + + return result .. "))" +end + + + +-- Done + +return Graph diff --git a/graphics/pgf/base/tex/generic/graphdrawing/lua/pgf/gd/deprecated/Iterators.lua b/graphics/pgf/base/tex/generic/graphdrawing/lua/pgf/gd/deprecated/Iterators.lua new file mode 100644 index 0000000000..903291a892 --- /dev/null +++ b/graphics/pgf/base/tex/generic/graphdrawing/lua/pgf/gd/deprecated/Iterators.lua @@ -0,0 +1,91 @@ +-- Copyright 2011 by Jannis Pohlmann +-- Copyright 2012 by Till Tantau +-- +-- This file may be distributed an/or modified +-- +-- 1. under the LaTeX Project Public License and/or +-- 2. under the GNU Public License +-- +-- See the file doc/generic/pgf/licenses/LICENSE for more information + +-- @release $Header$ + + + +--- The Iterators class is a singleton object. +-- +-- It provides advanced iterators. + +local Iterators = {} + +-- Namespace + +local lib = require("pgf.gd.lib") + + + +--- Iterator for traversing a \meta{dag} using a topological sorting. +-- +-- A topological sorting of a directed graph is a linear ordering of its +-- nodes such that, for every edge |(u,v)|, |u| comes before |v|. +-- +-- Important note: if performed on a graph with at least one cycle a +-- topological sorting is impossible. Thus, the nodes returned from the +-- iterator are not guaranteed to satisfy the ``|u| comes before |v|'' +-- criterion. The iterator may even terminate early or loop forever. +-- +-- @param graph A directed acyclic graph. +-- +-- @return An iterator for traversing \meta{graph} in a topological order. +-- +function Iterators.topologicallySorted(dag) + -- track visited edges + local deleted_edges = {} + + -- collect all sources (nodes with no incoming edges) of the dag + local sources = lib.imap(dag.nodes, function (node) if node:getInDegree() == 0 then return node end end) + + -- return the iterator function + return function () + while #sources > 0 do + -- fetch the next sink from the queue + local source = table.remove(sources, 1) + + -- get its outgoing edges + local out_edges = source:getOutgoingEdges() + + -- iterate over all outgoing edges we haven't visited yet + for _,edge in ipairs(out_edges) do + if not deleted_edges[edge] then + -- mark the edge as visited + deleted_edges[edge] = true + + -- get the node at the other end of the edge + local neighbour = edge:getNeighbour(source) + + -- get a list of all incoming edges of the neighbor that have + -- not been visited yet + local in_edges = lib.imap(neighbour:getIncomingEdges(), + function (edge) if not deleted_edges[edge] then return edge end end) + + -- if there are no such edges then we have a new source + if #in_edges == 0 then + sources[#sources+1] = neighbour + end + end + end + + -- return the current source + return source + end + + -- the iterator terminates if there are no sources left + return nil + end +end + + + +-- Done + +return Iterators diff --git a/graphics/pgf/base/tex/generic/graphdrawing/lua/pgf/gd/deprecated/Node.lua b/graphics/pgf/base/tex/generic/graphdrawing/lua/pgf/gd/deprecated/Node.lua new file mode 100644 index 0000000000..379a65fafd --- /dev/null +++ b/graphics/pgf/base/tex/generic/graphdrawing/lua/pgf/gd/deprecated/Node.lua @@ -0,0 +1,280 @@ +-- Copyright 2010 by Renée Ahrens, Olof Frahm, Jens Kluttig, Matthias Schulz, Stephan Schuster +-- Copyright 2011 by Jannis Pohlmann +-- Copyright 2012 by Till Tantau +-- +-- This file may be distributed an/or modified +-- +-- 1. under the LaTeX Project Public License and/or +-- 2. under the GNU Public License +-- +-- See the file doc/generic/pgf/licenses/LICENSE for more information + +-- @release $Header$ + + + +--- The Node class +-- +-- + +local Node = {} +Node.__index = Node + + +-- Namespace + +local lib = require "pgf.gd.lib" + + +-- Imports + +local Vector = require "pgf.gd.deprecated.Vector" + + + +--- Creates a new node. +-- +-- @param values Values to override default node settings. +-- The following parameters can be set:\par +-- |name|: The name of the node. It is obligatory to define this.\par +-- |tex|: Information about the corresponding \TeX\ node.\par +-- |edges|: Edges adjacent to the node.\par +-- |pos|: Initial position of the node.\par +-- |options|: A table of node options passed over from \tikzname. +-- +-- @return A newly allocated node. +-- +function Node.new(values) + local new = { + class = Node, + name = nil, + tex = { + -- texNode = nil, + -- maxX = nil, + -- minX = nil, + -- maxY = nil, + -- minY = nil + }, + edges = {}, + -- pos = nil, + options = {}, + -- growth_direction = nil, + -- index = nil, + -- event_index = nil, + kind = "node", + } + setmetatable(new, Node) + if values then + for k,v in pairs(values) do + new [k] = v + end + end + if not new.pos then + new.pos = Vector.new(2) + end + return new +end + + + +--- Sets the node option \meta{name} to \meta{value}. +-- +-- @param name Name of the node option to be changed. +-- @param value New value for the node option \meta{name}. +-- +function Node:setOption(name, value) + self.options[name] = value +end + + + +--- Returns the value of the node option \meta{name}. +-- +-- @param name Name of the node option. +-- @param graph If this optional argument is given, +-- in case the option is not set as a node parameter, +-- we try to look it up as a graph parameter. +-- +-- @return The value of the node option \meta{name} or |nil|. +-- +function Node:getOption(name, graph) + return lib.lookup_option(name, self, graph) +end + + + +--- Computes the width of the node. +-- +-- @return Width of the node. +-- +function Node:getTexWidth() + return math.abs(self.tex.maxX - self.tex.minX) +end + + + +--- Computes the height of the node. +-- +-- @return Height of the node. +-- +function Node:getTexHeight() + return math.abs(self.tex.maxY - self.tex.minY) +end + + + +--- Adds new edge to the node. +-- +-- @param edge The edge to be added. +-- +function Node:addEdge(edge) + table.insert(self.edges, edge) +end + + + +--- Removes an edge from the node. +-- +-- @param edge The edge to remove. +-- +function Node:removeEdge(edge) + self.edges = lib.imap (self.edges, function(other) if other ~= edge then return other end end) +end + + + +--- Counts the adjacent edges of the node. +-- +-- @return The number of adjacent edges of the node. +-- +function Node:getDegree() + return #self.edges +end + + + +--- Returns all edges of the node. +-- +-- Instead of calling |node:getEdges()| the edges can alternatively be +-- accessed directly with |node.edges|. +-- +-- @return All edges of the node. +-- +function Node:getEdges() + return self.edges +end + + + +--- Returns the incoming edges of the node. Undefined result for hyperedges. +-- +-- @param ignore_reversed Optional parameter to consider reversed edges not +-- reversed for this method call. Defaults to |false|. +-- +-- @return Incoming edges of the node. This includes undirected edges +-- and directed edges pointing to the node. +-- +function Node:getIncomingEdges(ignore_reversed) + return lib.imap(self.edges, + function (edge) + if edge:isHead(self, ignore_reversed) then return edge end + end) +end + + + +--- Returns the outgoing edges of the node. Undefined result for hyperedges. +-- +-- @param ignore_reversed Optional parameter to consider reversed edges not +-- reversed for this method call. Defaults to |false|. +-- +-- @return Outgoing edges of the node. This includes undirected edges +-- and directed edges leaving the node. +-- +function Node:getOutgoingEdges(ignore_reversed) + return lib.imap(self.edges, + function (edge) + if edge:isTail(self, ignore_reversed) then return edge end + end) +end + + + +--- Returns the number of incoming edges of the node. +-- +-- @see Node:getIncomingEdges(reversed) +-- +-- @param ignore_reversed Optional parameter to consider reversed edges not +-- reversed for this method call. Defaults to |false|. +-- +-- @return The number of incoming edges of the node. +-- +function Node:getInDegree(ignore_reversed) + return #self:getIncomingEdges(ignore_reversed) +end + + + +--- Returns the number of edges starting at the node. +-- +-- @see Node:getOutgoingEdges() +-- +-- @param ignore_reversed Optional parameter to consider reversed edges not +-- reversed for this method call. Defaults to |false|. +-- +-- @return The number of outgoing edges of the node. +-- +function Node:getOutDegree(ignore_reversed) + return #self:getOutgoingEdges(ignore_reversed) +end + + + +--- Creates a shallow copy of the node. +-- +-- Most notably, the edges adjacent are not preserved in the copy. +-- +-- @return Copy of the node. +-- +function Node:copy() + local result = lib.copy(self, Node.new()) + result.edges = {} + return result +end + + + +--- Compares two nodes by their name. +-- +-- @ignore This should not appear in the documentation. +-- +-- @param other Another node to compare with. +-- +-- @return |true| if both nodes have the same name. |false| otherwise. +-- +function Node:__eq(object) + return self.name == object.name +end + + + +--- Returns a formated string representation of the node. +-- +-- @ignore This should not appear in the documentation. +-- +-- @return String representation of the node. +-- +function Node:__tostring() + local tmp = Node.__tostring + Node.__tostring = nil + local result = "Node<" .. tostring(self) .. ">(" .. self.name .. ")" + Node.__tostring = tmp + return result +end + + + + +-- Done + +return Node diff --git a/graphics/pgf/base/tex/generic/graphdrawing/lua/pgf/gd/deprecated/Vector.lua b/graphics/pgf/base/tex/generic/graphdrawing/lua/pgf/gd/deprecated/Vector.lua new file mode 100644 index 0000000000..8af7378154 --- /dev/null +++ b/graphics/pgf/base/tex/generic/graphdrawing/lua/pgf/gd/deprecated/Vector.lua @@ -0,0 +1,256 @@ +-- Copyright 2012 by Till Tantau +-- +-- This file may be distributed an/or modified +-- +-- 1. under the LaTeX Project Public License and/or +-- 2. under the GNU Public License +-- +-- See the file doc/generic/pgf/licenses/LICENSE for more information + +-- @release $Header$ + + +--- Vector class +-- +-- This class augments a normal array so that: +-- +-- 1) Several functions like "plus" or "normalize" become available. +-- 2) You can access the ".x" and ".y" fields to get the fields [1] and [2]. + +local Vector = {} + + +-- Namespace: +local lib = require "pgf.gd.lib" +lib.Vector = Vector + + +-- Class setup +Vector.__index = + function (t, k) + if k == "x" then + return rawget(t,1) + elseif k == "y" then + return rawget(t,2) + else + return rawget(Vector,k) + end + end +Vector.__newindex = + function (t, k, v) + if k == "x" then + rawset(t,1,v) + elseif k == "y" then + rawset(t,2,v) + else + rawset(t,k,v) + end + end + + + +--- Creates a new vector with \meta{n} values using an optional \meta{fill\_function}. +-- +-- @param n The number of elements of the vector. (If omitted, then 2.) +-- @param fill_function Optional function that takes a number between 1 and \meta{n} +-- and is expected to return a value for the corresponding element +-- of the vector. If omitted, all elements of the vector will +-- be initialized with 0. +-- +-- @return A newly-allocated vector with \meta{n} elements. +-- +function Vector.new(n, fill_function) + -- create vector + local vector = { } + setmetatable(vector, Vector) + + local n = n or 2 + + if type(n) == 'table' then + for k,v in pairs(n) do + vector[k] = v + end + else + -- fill vector elements with values + if not fill_function then + for i = 1,n do + rawset(vector,i,0) + end + else + for i = 1,n do + rawset(vector,i,fill_function(i)) + end + end + end + + return vector +end + + + +--- Creates a copy of the vector that holds the same elements as the original. +-- +-- @return A newly-allocated copy of the vector holding exactly the same elements. +-- +function Vector:copy() + return Vector.new(#self, function (n) return self[n] end) +end + + + +--- Performs a vector addition and returns the result in a new vector. +-- +-- @param other The vector to add. +-- +-- @return A new vector with the result of the addition. +-- +function Vector:plus(other) + assert(#self == #other) + + return Vector.new(#self, function (n) return self[n] + other[n] end) +end + + + +--- Subtracts two vectors and returns the result in a new vector. +-- +-- @param other Vector to subtract. +-- +-- @return A new vector with the result of the subtraction. +-- +function Vector:minus(other) + assert(#self == #other) + + return Vector.new(#self, function (n) return self[n] - other[n] end) +end + + + +--- Divides a vector by a scalar value and returns the result in a new vector. +-- +-- @param scalar Scalar value to divide the vector by. +-- +-- @return A new vector with the result of the division. +-- +function Vector:dividedByScalar(scalar) + return Vector.new(#self, function (n) return self[n] / scalar end) +end + + + +--- Multiplies a vector by a scalar value and returns the result in a new vector. +-- +-- @param scalar Scalar value to multiply the vector with. +-- +-- @return A new vector with the result of the multiplication. +-- +function Vector:timesScalar(scalar) + return Vector.new(#self, function (n) return self[n] * scalar end) +end + + + +--- Performs the dot product of two vectors and returns the result in a new vector. +-- +-- @param other Vector to perform the dot product with. +-- +-- @return A new vector with the result of the dot product. +-- +function Vector:dotProduct(other) + assert(#self == #other) + + local product = 0 + for n = 1,#self do + product = product + self[n] * other[n] + end + return product +end + + + +--- Computes the Euclidean norm of the vector. +-- +-- @return The Euclidean norm of the vector. +-- +function Vector:norm() + return math.sqrt(self:dotProduct(self)) +end + + + +--- Normalizes the vector and returns the result in a new vector. +-- +-- @return Normalized version of the original vector. +-- +function Vector:normalized() + local norm = self:norm() + if norm == 0 then + return Vector.new(#self) + else + return self:dividedByScalar(self:norm()) + end +end + + + +--- Updates the values of the vector in-place. +-- +-- @param update_function A function that is called for each element of the +-- vector. The elements are replaced by the values +-- returned from this function. +-- +function Vector:update(update_function) + for i=1,#self do + self[i] = update_function(self[i]) + end +end + + + +--- Limits all elements of the vector in-place. +-- +-- @param limit_function A function that is called for each index/element +-- pair. It is supposed to return minimum and maximum +-- values for the element. The element is then clamped +-- to these values. +-- +function Vector:limit(limit_function) + for i=1,#self do + local min, max = limit_function(i, self[i]) + self[i] = math.max(min, math.min(max, value)) + end +end + + +--- Tests whether all elements of two vectors are the same +-- +-- @param other The other vector +-- +-- @return true or false +-- +function Vector:equals(other) + if #self ~= #other then + return false + end + + for n = 1, #self do + if self[n] ~= other[n] then + return false + end + end + + return true +end + + +function Vector:__tostring() + return '(' .. table.concat(self, ', ') .. ')' +end + + + + + +-- Done + +return Vector diff --git a/graphics/pgf/base/tex/generic/graphdrawing/lua/pgf/gd/doc.lua b/graphics/pgf/base/tex/generic/graphdrawing/lua/pgf/gd/doc.lua new file mode 100644 index 0000000000..78b68bbcb1 --- /dev/null +++ b/graphics/pgf/base/tex/generic/graphdrawing/lua/pgf/gd/doc.lua @@ -0,0 +1,124 @@ +-- Copyright 2013 by Till Tantau +-- +-- This file may be distributed an/or modified +-- +-- 1. under the LaTeX Project Public License and/or +-- 2. under the GNU Public License +-- +-- See the file doc/generic/pgf/licenses/LICENSE for more information + +-- @release $Header$ + + +--- +-- The table doc is used for documentation purposes. It is used to +-- provide lazy documentation for keys, that is, to install +-- documentation for keys only when this information is requested and +-- when the documentation is kept in a separate file. +-- +-- Using the doc facility is easy: +-- % +-- \begin{enumerate} +-- \item In the |declare| statement of the key, you do not provide +-- fields like |documentation| or |summary|. Rather, you provide the +-- field |documentation_in|, which gets the name of a Lua file the +-- will be read whenever one of the fields |documentation|, |summary|, +-- or |examples| is requested for the key. +-- \item When the key is requested, |require| will be applied to the +-- filename given in the |documentation_in| field. +-- \item In this file, you start with the following code: +-- % +--\begin{codeexample}[code only] +--local doc = require 'pgf.gd.doc' +--local key = doc.key +--local documentation = doc.documentation +--local summary = doc.summary +--local example = doc.example +--\end{codeexample} +-- % +-- This will setup nice shortcuts for the commands you are going to +-- use in your file. +-- \item Next, for each to-be-lazily-documented key, add a block to +-- the file like the following: +-- % +--\begin{codeexample}[code only] +-- --- +-- key "my radius" +-- summary "This key specifies a radius." +-- documentation +-- [[ +-- This key is used, whenever... +-- ]] +-- example "\tikz \graph [foo layout, my radius=5] { a--b };" +-- example "\tikz \graph [foo layout, my radius=3] { c--d };" +--\end{codeexample} +-- +-- Note that |[[| and |]]| are used in Lua for raw multi-line strings. +-- +-- The effect of the above code will be that for the key |my radius| +-- the different field like |summary| or |documentation| get +-- updated. The |key| function simple ``selects'' a key and subsequent +-- commands like |summary| will update this key until a different key +-- is selected through another use of |key|. +-- \end{enumerate} + +local doc = {} + +local current_key + + +-- Namespace +require "pgf.gd".doc = doc + + +-- Imports +local keys = require "pgf.gd.interface.InterfaceCore".keys + +--- +-- Selects the key which will be subsequently updated by the other +-- functions of this class. +-- +-- @param key A key. + +function doc.key (key) + current_key = assert(keys[key], "trying to document not-yet-declared key") +end + + +--- +-- Updates (replaces) the summary field of the last key selected +-- through the |key| command. +-- +-- @param string A (new) summary string. + +function doc.summary (string) + current_key.summary = string +end + + +--- +-- Updates (replaces) the documentation field of the last key selected +-- through the |key| command. +-- +-- @param string A (new) documentation string. Typically, the |[[| +-- syntax will be used to specify this string. + +function doc.documentation (string) + current_key.documentation = string +end + + +--- +-- Adds an example to the |examples| field of the last key selected +-- through the |key| command. +-- +-- @param string An additional example string. + +function doc.example (string) + local examples = rawget(current_key, "examples") or {} + examples[#examples + 1] = string + current_key.examples = examples +end + + +return doc diff --git a/graphics/pgf/base/tex/generic/graphdrawing/lua/pgf/gd/doc/ogdf/energybased.lua b/graphics/pgf/base/tex/generic/graphdrawing/lua/pgf/gd/doc/ogdf/energybased.lua new file mode 100644 index 0000000000..552dd00dc8 --- /dev/null +++ b/graphics/pgf/base/tex/generic/graphdrawing/lua/pgf/gd/doc/ogdf/energybased.lua @@ -0,0 +1,19 @@ +-- Copyright 2013 by Till Tantau +-- +-- This file may be distributed an/or modified +-- +-- 1. under the LaTeX Project Public License and/or +-- 2. under the GNU Public License +-- +-- See the file doc/generic/pgf/licenses/LICENSE for more information + +-- @release $Header$ + + + +--- +-- @section subsection {Force-Based Algorithms} +-- +-- + +local _
\ No newline at end of file diff --git a/graphics/pgf/base/tex/generic/graphdrawing/lua/pgf/gd/doc/ogdf/energybased/FMMMLayout.lua b/graphics/pgf/base/tex/generic/graphdrawing/lua/pgf/gd/doc/ogdf/energybased/FMMMLayout.lua new file mode 100644 index 0000000000..fcd5b26fb0 --- /dev/null +++ b/graphics/pgf/base/tex/generic/graphdrawing/lua/pgf/gd/doc/ogdf/energybased/FMMMLayout.lua @@ -0,0 +1,129 @@ +-- Copyright 2013 by Till Tantau +-- +-- This file may be distributed an/or modified +-- +-- 1. under the LaTeX Project Public License and/or +-- 2. under the GNU Public License +-- +-- See the file doc/generic/pgf/licenses/LICENSE for more information + +-- @release $Header$ + + +local key = require 'pgf.gd.doc'.key +local documentation = require 'pgf.gd.doc'.documentation +local summary = require 'pgf.gd.doc'.summary +local example = require 'pgf.gd.doc'.example + + +-------------------------------------------------------------------------------- +key "FMMMLayout" +summary "The fast multipole multilevel layout algorithm." + +documentation +[[ +|FMMMLayout| implements a force-directed graph drawing +method suited also for very large graphs. It is based on a +combination of an efficient multilevel scheme and a strategy for +approximating the repulsive forces in the system by rapidly +evaluating potential fields. + +The implementation is based on the following publication: +% +\begin{itemize} + \item Stefan Hachul, Michael J\"unger: Drawing Large Graphs with + a Potential-Field-Based Multilevel Algorithm. \emph{12th + International Symposium on Graph Drawing 1998 (GD '04)}, + New York, LNCS 3383, pp. 285--295, 2004. +\end{itemize} +]] + +example +[[ +\tikz \graph [FMMMLayout] { a -- {b,c,d} -- e -- a }; +]] + + +example +[[ +\tikz [nodes={text height=.7em, text depth=.2em, + draw=black!20, thick, fill=white, font=\footnotesize}, + >={Stealth[round,sep]}, rounded corners, semithick] + \graph [FMMMLayout, node sep=1mm, variation=2] { + "5th Edition" -> { "6th Edition", "PWB 1.0" }; + "6th Edition" -> { "LSX", "1 BSD", "Mini Unix", "Wollongong", "Interdata" }; + "Interdata" ->[orient=down] "Unix/TS 3.0", + "Interdata" -> { "PWB 2.0", "7th Edition" }; + "7th Edition" -> { "8th Edition", "32V", "V7M", "Ultrix-11", "Xenix", "UniPlus+" }; + "V7M" -> "Ultrix-11"; + "8th Edition" -> "9th Edition"; + "1 BSD" -> "2 BSD" -> "2.8 BSD" -> { "Ultrix-11", "2.9 BSD" }; + "32V" -> "3 BSD" -> "4 BSD" -> "4.1 BSD" -> { "4.2 BSD", "2.8 BSD", "8th Edition" }; + "4.2 BSD" -> { "4.3 BSD", "Ultrix-32" }; + "PWB 1.0" -> { "PWB 1.2" -> "PWB 2.0", "USG 1.0" -> { "CB Unix 1", "USG 2.0" }}; + "CB Unix 1" -> "CB Unix 2" -> "CB Unix 3" -> { "Unix/TS++", "PDP-11 Sys V" }; + { "USG 2.0" -> "USG 3.0", "PWB 2.0", "Unix/TS 1.0" } -> "Unix/TS 3.0"; + { "Unix/TS++", "CB Unix 3", "Unix/TS 3.0" } -> + "TS 4.0" -> "System V.0" -> "System V.2" -> "System V.3"; + }; +]] +-------------------------------------------------------------------------------- + + + +-------------------------------------------------------------------------------- +key "FMMMLayout.randSeed" +summary "Sets the random seed for the |FMMMLayout|." +documentation +[[ +By changing this number, you can vary the appearance of the generated +graph drawing. This key is an alias for |random seed|, which in turn +can be set by using the |variation| key. +]] + +example +[[ +\tikz \graph [FMMMLayout, variation=1] { a -- {b,c,d} -- e -- a }; +]] +example +[[ +\tikz \graph [FMMMLayout, variation=2] { a -- {b,c,d} -- e -- a }; +]] +-------------------------------------------------------------------------------- + + + +-------------------------------------------------------------------------------- +key "FMMMLayout.unitEdgeLength" +summary "The ``ideal'' padding between two nodes." + +documentation +[[ +The algorithm will try to make the padding between any two vertices +this distance. Naturally, this is not always possible, so, normally, +distance will actually be different. This key is an alias for the more +natural |node sep|. +]] + +example +[[ +\tikz { + \graph [FMMMLayout, node sep=1cm] { subgraph C_n[n=6]; }; + + \draw [red, ultra thick, |-|] (1.south) -- ++(down:1cm); +} +]] +example +[[ +\tikz { + \graph [FMMMLayout, node sep=5mm] { subgraph C_n[n=6]; }; + + \draw [red, ultra thick, |-|] (1.south) -- ++(down:5mm); +} +]] +-------------------------------------------------------------------------------- + + +-- Local Variables: +-- mode:latex +-- End: diff --git a/graphics/pgf/base/tex/generic/graphdrawing/lua/pgf/gd/doc/ogdf/energybased/FastMultipoleEmbedder.lua b/graphics/pgf/base/tex/generic/graphdrawing/lua/pgf/gd/doc/ogdf/energybased/FastMultipoleEmbedder.lua new file mode 100644 index 0000000000..453078b130 --- /dev/null +++ b/graphics/pgf/base/tex/generic/graphdrawing/lua/pgf/gd/doc/ogdf/energybased/FastMultipoleEmbedder.lua @@ -0,0 +1,51 @@ +-- Copyright 2013 by Till Tantau +-- +-- This file may be distributed an/or modified +-- +-- 1. under the LaTeX Project Public License and/or +-- 2. under the GNU Public License +-- +-- See the file doc/generic/pgf/licenses/LICENSE for more information + +-- @release $Header$ + + +local key = require 'pgf.gd.doc'.key +local documentation = require 'pgf.gd.doc'.documentation +local summary = require 'pgf.gd.doc'.summary +local example = require 'pgf.gd.doc'.example + + +-------------------------------------------------------------------------------- +key "FastMultipoleEmbedder" +summary "Implementation of a fast multipole embedder by Martin Gronemann." +-------------------------------------------------------------------------------- + + +-------------------------------------------------------------------------------- +key "FastMultipoleEmbedder.numIterations" +summary "sets the maximum number of iterations" +-------------------------------------------------------------------------------- + + +-------------------------------------------------------------------------------- +key "FastMultipoleEmbedder.multipolePrec" +summary "sets the number of coefficients for the expansions. default = 4" +-------------------------------------------------------------------------------- + + +-------------------------------------------------------------------------------- +key "FastMultipoleEmbedder.defaultEdgeLength" +summary "" +-------------------------------------------------------------------------------- + + +-------------------------------------------------------------------------------- +key "FastMultipoleEmbedder.defaultNodeSize" +summary "" +-------------------------------------------------------------------------------- + + +-- Local Variables: +-- mode:latex +-- End: diff --git a/graphics/pgf/base/tex/generic/graphdrawing/lua/pgf/gd/doc/ogdf/energybased/GEMLayout.lua b/graphics/pgf/base/tex/generic/graphdrawing/lua/pgf/gd/doc/ogdf/energybased/GEMLayout.lua new file mode 100644 index 0000000000..d0b94c2bb7 --- /dev/null +++ b/graphics/pgf/base/tex/generic/graphdrawing/lua/pgf/gd/doc/ogdf/energybased/GEMLayout.lua @@ -0,0 +1,103 @@ +-- Copyright 2013 by Till Tantau +-- +-- This file may be distributed an/or modified +-- +-- 1. under the LaTeX Project Public License and/or +-- 2. under the GNU Public License +-- +-- See the file doc/generic/pgf/licenses/LICENSE for more information + +-- @release $Header$ + + +local key = require 'pgf.gd.doc'.key +local documentation = require 'pgf.gd.doc'.documentation +local summary = require 'pgf.gd.doc'.summary +local example = require 'pgf.gd.doc'.example + + +-------------------------------------------------------------------------------- +key "GEMLayout" +summary "The energy-based GEM layout algorithm." + +documentation [[ + The implementation used in |GEMLayout| is based on the following publication: + % + \begin{itemize} + \item Arne Frick, Andreas Ludwig, Heiko Mehldau: \emph{A Fast Adaptive Layout + Algorithm for Undirected Graphs.} Proc. Graph Drawing 1994, + LNCS 894, pp. 388-403, 1995. + \end{itemize} +]] +-------------------------------------------------------------------------------- + + +-------------------------------------------------------------------------------- +key "GEMLayout.numberOfRounds" +summary "Sets the maximal number of round per node." +-------------------------------------------------------------------------------- + + +-------------------------------------------------------------------------------- +key "GEMLayout.minimalTemperature" +summary "Sets the minimal temperature." +-------------------------------------------------------------------------------- + + +-------------------------------------------------------------------------------- +key "GEMLayout.initialTemperature" +summary "Sets the initial temperature; must be $\\ge$ |minimalTemperature|." +-------------------------------------------------------------------------------- + + +-------------------------------------------------------------------------------- +key "GEMLayout.gravitationalConstant" +summary "Sets the gravitational constant; must be $\\ge 0$." +-------------------------------------------------------------------------------- + + +-------------------------------------------------------------------------------- +key "GEMLayout.desiredLength" +summary "Sets the desired edge length; must be $\\ge 0$." +-------------------------------------------------------------------------------- + + +-------------------------------------------------------------------------------- +key "GEMLayout.maximalDisturbance" +summary "Sets the maximal disturbance; must be $\\ge 0$." +-------------------------------------------------------------------------------- + + +-------------------------------------------------------------------------------- +key "GEMLayout.rotationAngle" +summary "Sets the opening angle for rotations ($0 \\le x \\le \\pi / 2$)." +-------------------------------------------------------------------------------- + + +-------------------------------------------------------------------------------- +key "GEMLayout.oscillationAngle" +summary "Sets the opening angle for oscillations ($0 \\le x \\le \\pi / 2$)." +-------------------------------------------------------------------------------- + + +-------------------------------------------------------------------------------- +key "GEMLayout.rotationSensitivity" +summary "Sets the rotation sensitivity ($0 \\le x \\le 1$)." +-------------------------------------------------------------------------------- + + +-------------------------------------------------------------------------------- +key "GEMLayout.oscillationSensitivity" +summary "Sets the oscillation sensitivity ($0 \\le x \\le 1$)." +-------------------------------------------------------------------------------- + + +-------------------------------------------------------------------------------- +key "GEMLayout.attractionFormula" +summary "sets the formula for attraction (1 = Fruchterman / Reingold, 2 = GEM)." +-------------------------------------------------------------------------------- + + +-- Local Variables: +-- mode:latex +-- End: diff --git a/graphics/pgf/base/tex/generic/graphdrawing/lua/pgf/gd/doc/ogdf/energybased/MultilevelLayout.lua b/graphics/pgf/base/tex/generic/graphdrawing/lua/pgf/gd/doc/ogdf/energybased/MultilevelLayout.lua new file mode 100644 index 0000000000..6210c93941 --- /dev/null +++ b/graphics/pgf/base/tex/generic/graphdrawing/lua/pgf/gd/doc/ogdf/energybased/MultilevelLayout.lua @@ -0,0 +1,33 @@ +-- Copyright 2013 by Till Tantau +-- +-- This file may be distributed an/or modified +-- +-- 1. under the LaTeX Project Public License and/or +-- 2. under the GNU Public License +-- +-- See the file doc/generic/pgf/licenses/LICENSE for more information + +-- @release $Header$ + + +local key = require 'pgf.gd.doc'.key +local documentation = require 'pgf.gd.doc'.documentation +local summary = require 'pgf.gd.doc'.summary +local example = require 'pgf.gd.doc'.example + + +-------------------------------------------------------------------------------- +key "MultilevelLayout" +summary "A wrapper for the multilevel layout computation using the modular multi-level mixer." + +example [[ +\tikz \graph [MultilevelLayout] { + a -- b -- c -- a -- d -- e -- f -- a +}; +]] +-------------------------------------------------------------------------------- + + +-- Local Variables: +-- mode:latex +-- End: diff --git a/graphics/pgf/base/tex/generic/graphdrawing/lua/pgf/gd/doc/ogdf/energybased/SpringEmbedderFR.lua b/graphics/pgf/base/tex/generic/graphdrawing/lua/pgf/gd/doc/ogdf/energybased/SpringEmbedderFR.lua new file mode 100644 index 0000000000..ae3cf9c8ef --- /dev/null +++ b/graphics/pgf/base/tex/generic/graphdrawing/lua/pgf/gd/doc/ogdf/energybased/SpringEmbedderFR.lua @@ -0,0 +1,53 @@ +-- Copyright 2013 by Till Tantau +-- +-- This file may be distributed an/or modified +-- +-- 1. under the LaTeX Project Public License and/or +-- 2. under the GNU Public License +-- +-- See the file doc/generic/pgf/licenses/LICENSE for more information + +-- @release $Header$ + + +local key = require 'pgf.gd.doc'.key +local documentation = require 'pgf.gd.doc'.documentation +local summary = require 'pgf.gd.doc'.summary +local example = require 'pgf.gd.doc'.example + + +-------------------------------------------------------------------------------- +key "SpringEmbedderFR" +summary "The spring-embedder layout algorithm by Fruchterman and Reingold." + +documentation [[ + The implementation used in SpringEmbedderFR is based on the following + publication: + + Thomas M. J. Fruchterman, Edward M. Reingold: \emph{Graph Drawing by Force-directed + Placement}. Software - Practice and Experience 21(11), pp. 1129--1164, 1991. +]] +-------------------------------------------------------------------------------- + + +-------------------------------------------------------------------------------- +key "SpringEmbedderFR.iterations" +summary "Sets the number of iterations." +-------------------------------------------------------------------------------- + + +-------------------------------------------------------------------------------- +key "SpringEmbedderFR.noise" +summary "Sets the parameter noise." +-------------------------------------------------------------------------------- + + +-------------------------------------------------------------------------------- +key "SpringEmbedderFR.scaleFunctionFactor" +summary "Sets the scale function factor." +-------------------------------------------------------------------------------- + + +-- Local Variables: +-- mode:latex +-- End: diff --git a/graphics/pgf/base/tex/generic/graphdrawing/lua/pgf/gd/doc/ogdf/energybased/SpringEmbedderFRExact.lua b/graphics/pgf/base/tex/generic/graphdrawing/lua/pgf/gd/doc/ogdf/energybased/SpringEmbedderFRExact.lua new file mode 100644 index 0000000000..6286e9f395 --- /dev/null +++ b/graphics/pgf/base/tex/generic/graphdrawing/lua/pgf/gd/doc/ogdf/energybased/SpringEmbedderFRExact.lua @@ -0,0 +1,57 @@ +-- Copyright 2013 by Till Tantau +-- +-- This file may be distributed an/or modified +-- +-- 1. under the LaTeX Project Public License and/or +-- 2. under the GNU Public License +-- +-- See the file doc/generic/pgf/licenses/LICENSE for more information + +-- @release $Header$ + + +local key = require 'pgf.gd.doc'.key +local documentation = require 'pgf.gd.doc'.documentation +local summary = require 'pgf.gd.doc'.summary +local example = require 'pgf.gd.doc'.example + + +-------------------------------------------------------------------------------- +key "SpringEmbedderFRExact" +summary "Declaration of Spring-Embedder (Fruchterman,Reingold) algorithm with exact force computations." +-------------------------------------------------------------------------------- + + +-------------------------------------------------------------------------------- +key "SpringEmbedderFRExact.iterations" +summary "Sets the number of iterations." +-------------------------------------------------------------------------------- + + +-------------------------------------------------------------------------------- +key "SpringEmbedderFRExact.noise" +summary "Sets the parameter noise." +-------------------------------------------------------------------------------- + + +-------------------------------------------------------------------------------- +key "SpringEmbedderFRExact.coolingFunction" +summary "Sets the parameter coolingFunction to |factor| or to |logarithmic|." +-------------------------------------------------------------------------------- + + +-------------------------------------------------------------------------------- +key "SpringEmbedderFRExact.idealEdgeLength" +summary "Sets the ideal edge length to a length." +-------------------------------------------------------------------------------- + + +-------------------------------------------------------------------------------- +key "SpringEmbedderFRExact.convTolerance" +summary "" +-------------------------------------------------------------------------------- + + +-- Local Variables: +-- mode:latex +-- End: diff --git a/graphics/pgf/base/tex/generic/graphdrawing/lua/pgf/gd/doc/ogdf/energybased/SpringEmbedderKK.lua b/graphics/pgf/base/tex/generic/graphdrawing/lua/pgf/gd/doc/ogdf/energybased/SpringEmbedderKK.lua new file mode 100644 index 0000000000..a4fd46aa77 --- /dev/null +++ b/graphics/pgf/base/tex/generic/graphdrawing/lua/pgf/gd/doc/ogdf/energybased/SpringEmbedderKK.lua @@ -0,0 +1,62 @@ +-- Copyright 2013 by Till Tantau +-- +-- This file may be distributed an/or modified +-- +-- 1. under the LaTeX Project Public License and/or +-- 2. under the GNU Public License +-- +-- See the file doc/generic/pgf/licenses/LICENSE for more information + +-- @release $Header$ + + +local key = require 'pgf.gd.doc'.key +local documentation = require 'pgf.gd.doc'.documentation +local summary = require 'pgf.gd.doc'.summary +local example = require 'pgf.gd.doc'.example + + +-------------------------------------------------------------------------------- +key "SpringEmbedderKK" +summary "The spring embedder of Kamada and Kawai" + +documentation [[ + The implementation used in |SpringEmbedderKK| is based on + the following publication: + + Tomihisa Kamada, Satoru Kawai: \emph{An Algorithm for Drawing + General Undirected Graphs.} Information Processing Letters 31, pp. 7--15, 1989. + + There are some parameters that can be tuned to optimize the + algorithm's behavior regarding runtime and layout quality. + First of all note that the algorithm uses all pairs shortest path + to compute the graph theoretic distance. This can be done either + with BFS (ignoring node sizes) in quadratic time or by using + e.g. Floyd's algorithm in cubic time with given edge lengths + that may reflect the node sizes. Also |m_computeMaxIt| decides + if the computation is stopped after a fixed maximum number of + iterations. The desirable edge length can either be set or computed + from the graph and the given layout. +]] +-------------------------------------------------------------------------------- + + +-------------------------------------------------------------------------------- +key "SpringEmbedderKK.stopTolerance" +summary "Sets the value for the stop tolerance." +documentation [[ + Below this value, the system is regarded stable (balanced) and the + optimization stopped. +]] +-------------------------------------------------------------------------------- + + +-------------------------------------------------------------------------------- +key "SpringEmbedderKK.desLength" +summary "Sets desirable edge length directly" +-------------------------------------------------------------------------------- + + +-- Local Variables: +-- mode:latex +-- End: diff --git a/graphics/pgf/base/tex/generic/graphdrawing/lua/pgf/gd/doc/ogdf/energybased/multilevelmixer/BarycenterPlacer.lua b/graphics/pgf/base/tex/generic/graphdrawing/lua/pgf/gd/doc/ogdf/energybased/multilevelmixer/BarycenterPlacer.lua new file mode 100644 index 0000000000..e658a896bd --- /dev/null +++ b/graphics/pgf/base/tex/generic/graphdrawing/lua/pgf/gd/doc/ogdf/energybased/multilevelmixer/BarycenterPlacer.lua @@ -0,0 +1,33 @@ +-- Copyright 2013 by Till Tantau +-- +-- This file may be distributed an/or modified +-- +-- 1. under the LaTeX Project Public License and/or +-- 2. under the GNU Public License +-- +-- See the file doc/generic/pgf/licenses/LICENSE for more information + +-- @release $Header$ + + +local key = require 'pgf.gd.doc'.key +local documentation = require 'pgf.gd.doc'.documentation +local summary = require 'pgf.gd.doc'.summary +local example = require 'pgf.gd.doc'.example + + +-------------------------------------------------------------------------------- +key "BarycenterPlacer" +summary "?" +-------------------------------------------------------------------------------- + + +-------------------------------------------------------------------------------- +key "BarycenterPlacer.weightedPositionPriority" +summary "" +-------------------------------------------------------------------------------- + + +-- Local Variables: +-- mode:latex +-- End: diff --git a/graphics/pgf/base/tex/generic/graphdrawing/lua/pgf/gd/doc/ogdf/energybased/multilevelmixer/CirclePlacer.lua b/graphics/pgf/base/tex/generic/graphdrawing/lua/pgf/gd/doc/ogdf/energybased/multilevelmixer/CirclePlacer.lua new file mode 100644 index 0000000000..e6f06add57 --- /dev/null +++ b/graphics/pgf/base/tex/generic/graphdrawing/lua/pgf/gd/doc/ogdf/energybased/multilevelmixer/CirclePlacer.lua @@ -0,0 +1,45 @@ +-- Copyright 2013 by Till Tantau +-- +-- This file may be distributed an/or modified +-- +-- 1. under the LaTeX Project Public License and/or +-- 2. under the GNU Public License +-- +-- See the file doc/generic/pgf/licenses/LICENSE for more information + +-- @release $Header$ + + +local key = require 'pgf.gd.doc'.key +local documentation = require 'pgf.gd.doc'.documentation +local summary = require 'pgf.gd.doc'.summary +local example = require 'pgf.gd.doc'.example + + +-------------------------------------------------------------------------------- +key "CirclePlacer" +summary "?" +-------------------------------------------------------------------------------- + + +-------------------------------------------------------------------------------- +key "CirclePlacer.circleSize" +summary "" +-------------------------------------------------------------------------------- + + +-------------------------------------------------------------------------------- +key "CirclePlacer.radiusFixed" +summary "" +-------------------------------------------------------------------------------- + + +-------------------------------------------------------------------------------- +key "CirclePlacer.nodeSelection" +summary "" +-------------------------------------------------------------------------------- + + +-- Local Variables: +-- mode:latex +-- End: diff --git a/graphics/pgf/base/tex/generic/graphdrawing/lua/pgf/gd/doc/ogdf/energybased/multilevelmixer/EdgeCoverMerger.lua b/graphics/pgf/base/tex/generic/graphdrawing/lua/pgf/gd/doc/ogdf/energybased/multilevelmixer/EdgeCoverMerger.lua new file mode 100644 index 0000000000..e8723272ee --- /dev/null +++ b/graphics/pgf/base/tex/generic/graphdrawing/lua/pgf/gd/doc/ogdf/energybased/multilevelmixer/EdgeCoverMerger.lua @@ -0,0 +1,33 @@ +-- Copyright 2013 by Till Tantau +-- +-- This file may be distributed an/or modified +-- +-- 1. under the LaTeX Project Public License and/or +-- 2. under the GNU Public License +-- +-- See the file doc/generic/pgf/licenses/LICENSE for more information + +-- @release $Header$ + + +local key = require 'pgf.gd.doc'.key +local documentation = require 'pgf.gd.doc'.documentation +local summary = require 'pgf.gd.doc'.summary +local example = require 'pgf.gd.doc'.example + + +-------------------------------------------------------------------------------- +key "EdgeCoverMerger" +summary "?" +-------------------------------------------------------------------------------- + + +-------------------------------------------------------------------------------- +key "EdgeCoverMerger.factor" +summary "" +-------------------------------------------------------------------------------- + + +-- Local Variables: +-- mode:latex +-- End: diff --git a/graphics/pgf/base/tex/generic/graphdrawing/lua/pgf/gd/doc/ogdf/energybased/multilevelmixer/IndependentSetMerger.lua b/graphics/pgf/base/tex/generic/graphdrawing/lua/pgf/gd/doc/ogdf/energybased/multilevelmixer/IndependentSetMerger.lua new file mode 100644 index 0000000000..81e2f37db0 --- /dev/null +++ b/graphics/pgf/base/tex/generic/graphdrawing/lua/pgf/gd/doc/ogdf/energybased/multilevelmixer/IndependentSetMerger.lua @@ -0,0 +1,33 @@ +-- Copyright 2013 by Till Tantau +-- +-- This file may be distributed an/or modified +-- +-- 1. under the LaTeX Project Public License and/or +-- 2. under the GNU Public License +-- +-- See the file doc/generic/pgf/licenses/LICENSE for more information + +-- @release $Header$ + + +local key = require 'pgf.gd.doc'.key +local documentation = require 'pgf.gd.doc'.documentation +local summary = require 'pgf.gd.doc'.summary +local example = require 'pgf.gd.doc'.example + + +-------------------------------------------------------------------------------- +key "IndependentSetMerger" +summary "?" +-------------------------------------------------------------------------------- + + +-------------------------------------------------------------------------------- +key "IndependentSetMerger.searchDepthBase" +summary "" +-------------------------------------------------------------------------------- + + +-- Local Variables: +-- mode:latex +-- End: diff --git a/graphics/pgf/base/tex/generic/graphdrawing/lua/pgf/gd/doc/ogdf/energybased/multilevelmixer/LocalBiconnectedMerger.lua b/graphics/pgf/base/tex/generic/graphdrawing/lua/pgf/gd/doc/ogdf/energybased/multilevelmixer/LocalBiconnectedMerger.lua new file mode 100644 index 0000000000..7fbca25e81 --- /dev/null +++ b/graphics/pgf/base/tex/generic/graphdrawing/lua/pgf/gd/doc/ogdf/energybased/multilevelmixer/LocalBiconnectedMerger.lua @@ -0,0 +1,33 @@ +-- Copyright 2013 by Till Tantau +-- +-- This file may be distributed an/or modified +-- +-- 1. under the LaTeX Project Public License and/or +-- 2. under the GNU Public License +-- +-- See the file doc/generic/pgf/licenses/LICENSE for more information + +-- @release $Header$ + + +local key = require 'pgf.gd.doc'.key +local documentation = require 'pgf.gd.doc'.documentation +local summary = require 'pgf.gd.doc'.summary +local example = require 'pgf.gd.doc'.example + + +-------------------------------------------------------------------------------- +key "LocalBiconnectedMerger" +summary "?" +-------------------------------------------------------------------------------- + + +-------------------------------------------------------------------------------- +key "LocalBiconnectedMerger.factor" +summary "" +-------------------------------------------------------------------------------- + + +-- Local Variables: +-- mode:latex +-- End: diff --git a/graphics/pgf/base/tex/generic/graphdrawing/lua/pgf/gd/doc/ogdf/energybased/multilevelmixer/MatchingMerger.lua b/graphics/pgf/base/tex/generic/graphdrawing/lua/pgf/gd/doc/ogdf/energybased/multilevelmixer/MatchingMerger.lua new file mode 100644 index 0000000000..e966b49056 --- /dev/null +++ b/graphics/pgf/base/tex/generic/graphdrawing/lua/pgf/gd/doc/ogdf/energybased/multilevelmixer/MatchingMerger.lua @@ -0,0 +1,33 @@ +-- Copyright 2013 by Till Tantau +-- +-- This file may be distributed an/or modified +-- +-- 1. under the LaTeX Project Public License and/or +-- 2. under the GNU Public License +-- +-- See the file doc/generic/pgf/licenses/LICENSE for more information + +-- @release $Header$ + + +local key = require 'pgf.gd.doc'.key +local documentation = require 'pgf.gd.doc'.documentation +local summary = require 'pgf.gd.doc'.summary +local example = require 'pgf.gd.doc'.example + + +-------------------------------------------------------------------------------- +key "MatchingMerger" +summary "?" +-------------------------------------------------------------------------------- + + +-------------------------------------------------------------------------------- +key "MatchingMerger.selectByNodeMass" +summary "" +-------------------------------------------------------------------------------- + + +-- Local Variables: +-- mode:latex +-- End: diff --git a/graphics/pgf/base/tex/generic/graphdrawing/lua/pgf/gd/doc/ogdf/energybased/multilevelmixer/MedianPlacer.lua b/graphics/pgf/base/tex/generic/graphdrawing/lua/pgf/gd/doc/ogdf/energybased/multilevelmixer/MedianPlacer.lua new file mode 100644 index 0000000000..0b9d967db9 --- /dev/null +++ b/graphics/pgf/base/tex/generic/graphdrawing/lua/pgf/gd/doc/ogdf/energybased/multilevelmixer/MedianPlacer.lua @@ -0,0 +1,27 @@ +-- Copyright 2013 by Till Tantau +-- +-- This file may be distributed an/or modified +-- +-- 1. under the LaTeX Project Public License and/or +-- 2. under the GNU Public License +-- +-- See the file doc/generic/pgf/licenses/LICENSE for more information + +-- @release $Header$ + + +local key = require 'pgf.gd.doc'.key +local documentation = require 'pgf.gd.doc'.documentation +local summary = require 'pgf.gd.doc'.summary +local example = require 'pgf.gd.doc'.example + + +-------------------------------------------------------------------------------- +key "MedianPlacer" +summary "?" +-------------------------------------------------------------------------------- + + +-- Local Variables: +-- mode:latex +-- End: diff --git a/graphics/pgf/base/tex/generic/graphdrawing/lua/pgf/gd/doc/ogdf/energybased/multilevelmixer/RandomMerger.lua b/graphics/pgf/base/tex/generic/graphdrawing/lua/pgf/gd/doc/ogdf/energybased/multilevelmixer/RandomMerger.lua new file mode 100644 index 0000000000..71bdbc7340 --- /dev/null +++ b/graphics/pgf/base/tex/generic/graphdrawing/lua/pgf/gd/doc/ogdf/energybased/multilevelmixer/RandomMerger.lua @@ -0,0 +1,33 @@ +-- Copyright 2013 by Till Tantau +-- +-- This file may be distributed an/or modified +-- +-- 1. under the LaTeX Project Public License and/or +-- 2. under the GNU Public License +-- +-- See the file doc/generic/pgf/licenses/LICENSE for more information + +-- @release $Header$ + + +local key = require 'pgf.gd.doc'.key +local documentation = require 'pgf.gd.doc'.documentation +local summary = require 'pgf.gd.doc'.summary +local example = require 'pgf.gd.doc'.example + + +-------------------------------------------------------------------------------- +key "RandomMerger" +summary "?" +-------------------------------------------------------------------------------- + + +-------------------------------------------------------------------------------- +key "RandomMerger.factor" +summary "" +-------------------------------------------------------------------------------- + + +-- Local Variables: +-- mode:latex +-- End: diff --git a/graphics/pgf/base/tex/generic/graphdrawing/lua/pgf/gd/doc/ogdf/energybased/multilevelmixer/RandomPlacer.lua b/graphics/pgf/base/tex/generic/graphdrawing/lua/pgf/gd/doc/ogdf/energybased/multilevelmixer/RandomPlacer.lua new file mode 100644 index 0000000000..c26946d4e1 --- /dev/null +++ b/graphics/pgf/base/tex/generic/graphdrawing/lua/pgf/gd/doc/ogdf/energybased/multilevelmixer/RandomPlacer.lua @@ -0,0 +1,33 @@ +-- Copyright 2013 by Till Tantau +-- +-- This file may be distributed an/or modified +-- +-- 1. under the LaTeX Project Public License and/or +-- 2. under the GNU Public License +-- +-- See the file doc/generic/pgf/licenses/LICENSE for more information + +-- @release $Header$ + + +local key = require 'pgf.gd.doc'.key +local documentation = require 'pgf.gd.doc'.documentation +local summary = require 'pgf.gd.doc'.summary +local example = require 'pgf.gd.doc'.example + + +-------------------------------------------------------------------------------- +key "RandomPlacer" +summary "?" +-------------------------------------------------------------------------------- + + +-------------------------------------------------------------------------------- +key "RandomPlacer.circleSize" +summary "" +-------------------------------------------------------------------------------- + + +-- Local Variables: +-- mode:latex +-- End: diff --git a/graphics/pgf/base/tex/generic/graphdrawing/lua/pgf/gd/doc/ogdf/energybased/multilevelmixer/SolarMerger.lua b/graphics/pgf/base/tex/generic/graphdrawing/lua/pgf/gd/doc/ogdf/energybased/multilevelmixer/SolarMerger.lua new file mode 100644 index 0000000000..76b084fa67 --- /dev/null +++ b/graphics/pgf/base/tex/generic/graphdrawing/lua/pgf/gd/doc/ogdf/energybased/multilevelmixer/SolarMerger.lua @@ -0,0 +1,39 @@ +-- Copyright 2013 by Till Tantau +-- +-- This file may be distributed an/or modified +-- +-- 1. under the LaTeX Project Public License and/or +-- 2. under the GNU Public License +-- +-- See the file doc/generic/pgf/licenses/LICENSE for more information + +-- @release $Header$ + + +local key = require 'pgf.gd.doc'.key +local documentation = require 'pgf.gd.doc'.documentation +local summary = require 'pgf.gd.doc'.summary +local example = require 'pgf.gd.doc'.example + + +-------------------------------------------------------------------------------- +key "SolarMerger" +summary "?" +-------------------------------------------------------------------------------- + + +-------------------------------------------------------------------------------- +key "SolarMerger.simple" +summary "" +-------------------------------------------------------------------------------- + + +-------------------------------------------------------------------------------- +key "SolarMerger.massAsNodeRadius" +summary "" +-------------------------------------------------------------------------------- + + +-- Local Variables: +-- mode:latex +-- End: diff --git a/graphics/pgf/base/tex/generic/graphdrawing/lua/pgf/gd/doc/ogdf/energybased/multilevelmixer/SolarPlacer.lua b/graphics/pgf/base/tex/generic/graphdrawing/lua/pgf/gd/doc/ogdf/energybased/multilevelmixer/SolarPlacer.lua new file mode 100644 index 0000000000..530ddbc0d8 --- /dev/null +++ b/graphics/pgf/base/tex/generic/graphdrawing/lua/pgf/gd/doc/ogdf/energybased/multilevelmixer/SolarPlacer.lua @@ -0,0 +1,27 @@ +-- Copyright 2013 by Till Tantau +-- +-- This file may be distributed an/or modified +-- +-- 1. under the LaTeX Project Public License and/or +-- 2. under the GNU Public License +-- +-- See the file doc/generic/pgf/licenses/LICENSE for more information + +-- @release $Header$ + + +local key = require 'pgf.gd.doc'.key +local documentation = require 'pgf.gd.doc'.documentation +local summary = require 'pgf.gd.doc'.summary +local example = require 'pgf.gd.doc'.example + + +-------------------------------------------------------------------------------- +key "SolarPlacer" +summary "?" +-------------------------------------------------------------------------------- + + +-- Local Variables: +-- mode:latex +-- End: diff --git a/graphics/pgf/base/tex/generic/graphdrawing/lua/pgf/gd/doc/ogdf/energybased/multilevelmixer/ZeroPlacer.lua b/graphics/pgf/base/tex/generic/graphdrawing/lua/pgf/gd/doc/ogdf/energybased/multilevelmixer/ZeroPlacer.lua new file mode 100644 index 0000000000..6f579d0a45 --- /dev/null +++ b/graphics/pgf/base/tex/generic/graphdrawing/lua/pgf/gd/doc/ogdf/energybased/multilevelmixer/ZeroPlacer.lua @@ -0,0 +1,33 @@ +-- Copyright 2013 by Till Tantau +-- +-- This file may be distributed an/or modified +-- +-- 1. under the LaTeX Project Public License and/or +-- 2. under the GNU Public License +-- +-- See the file doc/generic/pgf/licenses/LICENSE for more information + +-- @release $Header$ + + +local key = require 'pgf.gd.doc'.key +local documentation = require 'pgf.gd.doc'.documentation +local summary = require 'pgf.gd.doc'.summary +local example = require 'pgf.gd.doc'.example + + +-------------------------------------------------------------------------------- +key "ZeroPlacer" +summary "?" +-------------------------------------------------------------------------------- + + +-------------------------------------------------------------------------------- +key "ZeroPlacer.randomRange" +summary "" +-------------------------------------------------------------------------------- + + +-- Local Variables: +-- mode:latex +-- End: diff --git a/graphics/pgf/base/tex/generic/graphdrawing/lua/pgf/gd/doc/ogdf/layered.lua b/graphics/pgf/base/tex/generic/graphdrawing/lua/pgf/gd/doc/ogdf/layered.lua new file mode 100644 index 0000000000..ffd3ea844d --- /dev/null +++ b/graphics/pgf/base/tex/generic/graphdrawing/lua/pgf/gd/doc/ogdf/layered.lua @@ -0,0 +1,19 @@ +-- Copyright 2013 by Till Tantau +-- +-- This file may be distributed an/or modified +-- +-- 1. under the LaTeX Project Public License and/or +-- 2. under the GNU Public License +-- +-- See the file doc/generic/pgf/licenses/LICENSE for more information + +-- @release $Header$ + + + +--- +-- @section subsection {Algorithms for Drawing Layered Graphs} +-- +-- + +local _
\ No newline at end of file diff --git a/graphics/pgf/base/tex/generic/graphdrawing/lua/pgf/gd/doc/ogdf/layered/BarycenterHeuristic.lua b/graphics/pgf/base/tex/generic/graphdrawing/lua/pgf/gd/doc/ogdf/layered/BarycenterHeuristic.lua new file mode 100644 index 0000000000..5e4138eece --- /dev/null +++ b/graphics/pgf/base/tex/generic/graphdrawing/lua/pgf/gd/doc/ogdf/layered/BarycenterHeuristic.lua @@ -0,0 +1,23 @@ +-- Copyright 2013 by Till Tantau +-- +-- This file may be distributed an/or modified +-- +-- 1. under the LaTeX Project Public License and/or +-- 2. under the GNU Public License +-- +-- See the file doc/generic/pgf/licenses/LICENSE for more information + +-- @release $Header$ + + +local key = require 'pgf.gd.doc'.key +local documentation = require 'pgf.gd.doc'.documentation +local summary = require 'pgf.gd.doc'.summary +local example = require 'pgf.gd.doc'.example + + + +-------------------------------------------------------------------- +key "BarycenterHeuristic" +summary "The barycenter heuristic for 2-layer crossing minimization." +-------------------------------------------------------------------- diff --git a/graphics/pgf/base/tex/generic/graphdrawing/lua/pgf/gd/doc/ogdf/layered/CoffmanGrahamRanking.lua b/graphics/pgf/base/tex/generic/graphdrawing/lua/pgf/gd/doc/ogdf/layered/CoffmanGrahamRanking.lua new file mode 100644 index 0000000000..fd13e9bd4b --- /dev/null +++ b/graphics/pgf/base/tex/generic/graphdrawing/lua/pgf/gd/doc/ogdf/layered/CoffmanGrahamRanking.lua @@ -0,0 +1,35 @@ +-- Copyright 2013 by Till Tantau +-- +-- This file may be distributed an/or modified +-- +-- 1. under the LaTeX Project Public License and/or +-- 2. under the GNU Public License +-- +-- See the file doc/generic/pgf/licenses/LICENSE for more information + +-- @release $Header$ + + +local key = require 'pgf.gd.doc'.key +local documentation = require 'pgf.gd.doc'.documentation +local summary = require 'pgf.gd.doc'.summary +local example = require 'pgf.gd.doc'.example + + +-------------------------------------------------------------------- +key "CoffmanGrahamRanking" +summary "The ranking algorithm due to Coffman and Graham." +documentation +[[ +|CoffmanGrahamRanking| implements a node ranking algorithm based on +the Coffman--Graham scheduling algorithm, which can be used as first +phase in |SugiyamaLayout|. The aim of the algorithm is to ensure that +the height of the ranking (the number of layers) is kept small. +]] +-------------------------------------------------------------------- + + +-------------------------------------------------------------------- +key "CoffmanGrahamRanking.width" +summary "A mysterious width parameter..." +-------------------------------------------------------------------- diff --git a/graphics/pgf/base/tex/generic/graphdrawing/lua/pgf/gd/doc/ogdf/layered/DfsAcyclicSubgraph.lua b/graphics/pgf/base/tex/generic/graphdrawing/lua/pgf/gd/doc/ogdf/layered/DfsAcyclicSubgraph.lua new file mode 100644 index 0000000000..eced60b2ed --- /dev/null +++ b/graphics/pgf/base/tex/generic/graphdrawing/lua/pgf/gd/doc/ogdf/layered/DfsAcyclicSubgraph.lua @@ -0,0 +1,27 @@ +-- Copyright 2013 by Till Tantau +-- +-- This file may be distributed an/or modified +-- +-- 1. under the LaTeX Project Public License and/or +-- 2. under the GNU Public License +-- +-- See the file doc/generic/pgf/licenses/LICENSE for more information + +-- @release $Header$ + + +local key = require 'pgf.gd.doc'.key +local documentation = require 'pgf.gd.doc'.documentation +local summary = require 'pgf.gd.doc'.summary +local example = require 'pgf.gd.doc'.example + + + +-------------------------------------------------------------------- +key "DfsAcyclicSubgraph" +summary "DFS-based algorithm for computing a maximal acyclic subgraph." +documentation +[[ +The algorithm simply removes all DFS-backedges and works in linear-time. +]] +-------------------------------------------------------------------- diff --git a/graphics/pgf/base/tex/generic/graphdrawing/lua/pgf/gd/doc/ogdf/layered/FastHierarchyLayout.lua b/graphics/pgf/base/tex/generic/graphdrawing/lua/pgf/gd/doc/ogdf/layered/FastHierarchyLayout.lua new file mode 100644 index 0000000000..a7baad7e10 --- /dev/null +++ b/graphics/pgf/base/tex/generic/graphdrawing/lua/pgf/gd/doc/ogdf/layered/FastHierarchyLayout.lua @@ -0,0 +1,99 @@ +-- Copyright 2013 by Till Tantau +-- +-- This file may be distributed an/or modified +-- +-- 1. under the LaTeX Project Public License and/or +-- 2. under the GNU Public License +-- +-- See the file doc/generic/pgf/licenses/LICENSE for more information + +-- @release $Header$ + + +local key = require 'pgf.gd.doc'.key +local documentation = require 'pgf.gd.doc'.documentation +local summary = require 'pgf.gd.doc'.summary +local example = require 'pgf.gd.doc'.example + + +-------------------------------------------------------------------- +key "FastHierarchyLayout" +summary "Coordinate assignment phase for the Sugiyama algorithm by Buchheim et al." +documentation +[[ +This class implements a hierarchy layout algorithm, that is, it +layouts hierarchies with a given order of nodes on each +layer. It is used as a third phase of the Sugiyama algorithm. + +All edges of the layout will have at most two +bends. Additionally, for each edge having exactly two bends, the +segment between them is drawn vertically. This applies in +particular to the long edges arising in the first phase of the +Sugiyama algorithm. + +The implementation is based on: +% +\begin{itemize} + \item + Christoph Buchheim, Michael Jünger, Sebastian Leipert: A Fast + Layout Algorithm for k-Level Graphs. \emph{Proc. Graph + Drawing 2000}, volume 1984 of LNCS, pages 229--240, 2001. +\end{itemize} +]] + +example +[[ +\tikz \graph [SugiyamaLayout, FastHierarchyLayout] { + a -- {b,c,d} -- e -- a; +}; +]] +-------------------------------------------------------------------- + + + +-------------------------------------------------------------------- +key "FastHierarchyLayout.fixedLayerDistance" +summary "If true, the distance between neighbored layers is fixed, otherwise variable." +-------------------------------------------------------------------- + + + + +-------------------------------------------------------------------- +key "FastHierarchyLayout.layerDistance" +summary "Separation distance (padding) between two consecutive layers." + +documentation +[[ +Sets the (minimum?) padding between nodes on two consecutive +layers. It defaults to the sum of the keys +|level pre sep| and |level post sep|. +]] + +example +[[ +\tikz \graph [SugiyamaLayout, FastHierarchyLayout, + level sep=1cm] { + a -- {b,c,d} -- e -- a; +}; +]] +-------------------------------------------------------------------- + +-------------------------------------------------------------------- +key "FastHierarchyLayout.nodeDistance" +summary "Separation distance (padding) between two consecutive nodes on the same layer." + +documentation +[[ +Sets the (minimum?) padding between sibling nodes. It defaults to the +sum of the keys |sibling pre sep| and |sibling post sep|. +]] + +example +[[ +\tikz \graph [SugiyamaLayout, FastHierarchyLayout, + sibling sep=5mm] { + a -- {b,c,d} -- e -- a; +}; +]] +-------------------------------------------------------------------- diff --git a/graphics/pgf/base/tex/generic/graphdrawing/lua/pgf/gd/doc/ogdf/layered/FastSimpleHierarchyLayout.lua b/graphics/pgf/base/tex/generic/graphdrawing/lua/pgf/gd/doc/ogdf/layered/FastSimpleHierarchyLayout.lua new file mode 100644 index 0000000000..4af6daa620 --- /dev/null +++ b/graphics/pgf/base/tex/generic/graphdrawing/lua/pgf/gd/doc/ogdf/layered/FastSimpleHierarchyLayout.lua @@ -0,0 +1,99 @@ +-- Copyright 2013 by Till Tantau +-- +-- This file may be distributed an/or modified +-- +-- 1. under the LaTeX Project Public License and/or +-- 2. under the GNU Public License +-- +-- See the file doc/generic/pgf/licenses/LICENSE for more information + +-- @release $Header$ + + +local key = require 'pgf.gd.doc'.key +local documentation = require 'pgf.gd.doc'.documentation +local summary = require 'pgf.gd.doc'.summary +local example = require 'pgf.gd.doc'.example + + +-------------------------------------------------------------------- +key "FastSimpleHierarchyLayout" +summary "Coordinate assignment phase for the Sugiyama algorithm by Ulrik Brandes and Boris Köpf." + +documentation +[[ +This class implements a hierarchy layout algorithm, that is, it +layouts hierarchies with a given order of nodes on each +layer. It is used as a third phase of the Sugiyama algorithm. + +The algorithm runs in three phases: +% +\begin{enumerate} + \item Alignment (4x) + \item Horizontal Compactation (4x) + \item Balancing +\end{enumerate} +% +The alignment and horizontal compactification phases are calculated +downward, upward, left-to-right and right-to-left. The four +resulting layouts are combined in a balancing step. + +Warning: The implementation is known to not always produce a +correct layout. Therefore this Algorithm is for testing purpose +only. + +The implementation is based on: +% +\begin{itemize} + \item + Ulrik Brandes, Boris Köpf: Fast and Simple Horizontal + Coordinate Assignment. \emph{LNCS} 2002, Volume 2265/2002, + pp. 33--36 +\end{itemize} +]] + +example +[[ +\tikz \graph [SugiyamaLayout, FastSimpleHierarchyLayout] { + a -- {b,c,d} -- e -- a; +}; +]] +-------------------------------------------------------------------- + + + +-------------------------------------------------------------------- +key "FastSimpleHierarchyLayout.layerDistance" +summary "Distance between the centers of nodes of two consecutive layers." + +documentation +[[ +Sets the (minimum?) distance between nodes on two consecutive +layers. It defaults to the key |level distance|. +]] +example +[[ +\tikz \graph [SugiyamaLayout, FastSimpleHierarchyLayout, + level distance=2cm] { + a -- {b,c,d} -- e -- a; +}; +]] +-------------------------------------------------------------------- + +-------------------------------------------------------------------- +key "FastSimpleHierarchyLayout.siblingDistance" +summary "Distance between the centers of nodes of sibling nodes." + +documentation +[[ +Sets the (minimum?) padding between sibling nodes. It defaults to +|sibling distance|. +]] +example +[[ +\tikz \graph [SugiyamaLayout, FastSimpleHierarchyLayout, + sibling distance=5mm] { + a -- {b,c,d} -- e -- a; +}; +]] +-------------------------------------------------------------------- diff --git a/graphics/pgf/base/tex/generic/graphdrawing/lua/pgf/gd/doc/ogdf/layered/GreedyCycleRemoval.lua b/graphics/pgf/base/tex/generic/graphdrawing/lua/pgf/gd/doc/ogdf/layered/GreedyCycleRemoval.lua new file mode 100644 index 0000000000..d2f1d8eeea --- /dev/null +++ b/graphics/pgf/base/tex/generic/graphdrawing/lua/pgf/gd/doc/ogdf/layered/GreedyCycleRemoval.lua @@ -0,0 +1,27 @@ +-- Copyright 2013 by Till Tantau +-- +-- This file may be distributed an/or modified +-- +-- 1. under the LaTeX Project Public License and/or +-- 2. under the GNU Public License +-- +-- See the file doc/generic/pgf/licenses/LICENSE for more information + +-- @release $Header$ + + +local key = require 'pgf.gd.doc'.key +local documentation = require 'pgf.gd.doc'.documentation +local summary = require 'pgf.gd.doc'.summary +local example = require 'pgf.gd.doc'.example + + +-------------------------------------------------------------------- +key "GreedyCycleRemoval" +summary "Greedy algorithm for computing a maximal acyclic subgraph." +documentation +[[ + The algorithm applies a greedy heuristic to compute a maximal + acyclic subgraph and works in linear-time. +]] +-------------------------------------------------------------------- diff --git a/graphics/pgf/base/tex/generic/graphdrawing/lua/pgf/gd/doc/ogdf/layered/GreedyInsertHeuristic.lua b/graphics/pgf/base/tex/generic/graphdrawing/lua/pgf/gd/doc/ogdf/layered/GreedyInsertHeuristic.lua new file mode 100644 index 0000000000..a8cd94ef19 --- /dev/null +++ b/graphics/pgf/base/tex/generic/graphdrawing/lua/pgf/gd/doc/ogdf/layered/GreedyInsertHeuristic.lua @@ -0,0 +1,23 @@ +-- Copyright 2013 by Till Tantau +-- +-- This file may be distributed an/or modified +-- +-- 1. under the LaTeX Project Public License and/or +-- 2. under the GNU Public License +-- +-- See the file doc/generic/pgf/licenses/LICENSE for more information + +-- @release $Header$ + + +local key = require 'pgf.gd.doc'.key +local documentation = require 'pgf.gd.doc'.documentation +local summary = require 'pgf.gd.doc'.summary +local example = require 'pgf.gd.doc'.example + + + +-------------------------------------------------------------------- +key "GreedyInsertHeuristic" +summary "The greedy-insert heuristic for 2-layer crossing minimization." +-------------------------------------------------------------------- diff --git a/graphics/pgf/base/tex/generic/graphdrawing/lua/pgf/gd/doc/ogdf/layered/LongestPathRanking.lua b/graphics/pgf/base/tex/generic/graphdrawing/lua/pgf/gd/doc/ogdf/layered/LongestPathRanking.lua new file mode 100644 index 0000000000..3d98fbe9c6 --- /dev/null +++ b/graphics/pgf/base/tex/generic/graphdrawing/lua/pgf/gd/doc/ogdf/layered/LongestPathRanking.lua @@ -0,0 +1,57 @@ +-- Copyright 2013 by Till Tantau +-- +-- This file may be distributed an/or modified +-- +-- 1. under the LaTeX Project Public License and/or +-- 2. under the GNU Public License +-- +-- See the file doc/generic/pgf/licenses/LICENSE for more information + +-- @release $Header$ + + +local key = require 'pgf.gd.doc'.key +local documentation = require 'pgf.gd.doc'.documentation +local summary = require 'pgf.gd.doc'.summary +local example = require 'pgf.gd.doc'.example + + +-------------------------------------------------------------------- +key "LongestPathRanking" +summary "The longest-path ranking algorithm." +documentation +[[ + |LongestPathRanking| implements the well-known longest-path ranking + algorithm, which can be used as first phase in |SugiyamaLayout|. The + implementation contains a special optimization for reducing edge + lengths, as well as special treatment of mixed-upward graphs (for + instance, \textsc{uml} class diagrams). +]] +-------------------------------------------------------------------- + + +-------------------------------------------------------------------- +key "LongestPathRanking.separateDeg0Layer" +summary "If set to true, isolated nodes are placed on a separate layer." +-------------------------------------------------------------------- + + +-------------------------------------------------------------------- +key "LongestPathRanking.separateMultiEdges" +summary "If set to true, multi-edges will span at least two layers." +-------------------------------------------------------------------- + + + +-------------------------------------------------------------------- +key "LongestPathRanking.optimizeEdgeLength" +summary +[[ + If set to true the ranking algorithm tries to reduce edge + length even if this might increase the height of the layout. Choose + false, if the longest-path ranking known from the literature should be + used. +]] +-------------------------------------------------------------------- + + diff --git a/graphics/pgf/base/tex/generic/graphdrawing/lua/pgf/gd/doc/ogdf/layered/MedianHeuristic.lua b/graphics/pgf/base/tex/generic/graphdrawing/lua/pgf/gd/doc/ogdf/layered/MedianHeuristic.lua new file mode 100644 index 0000000000..b37480ac78 --- /dev/null +++ b/graphics/pgf/base/tex/generic/graphdrawing/lua/pgf/gd/doc/ogdf/layered/MedianHeuristic.lua @@ -0,0 +1,24 @@ +-- Copyright 2013 by Till Tantau +-- +-- This file may be distributed an/or modified +-- +-- 1. under the LaTeX Project Public License and/or +-- 2. under the GNU Public License +-- +-- See the file doc/generic/pgf/licenses/LICENSE for more information + +-- @release $Header$ + + +local key = require 'pgf.gd.doc'.key +local documentation = require 'pgf.gd.doc'.documentation +local summary = require 'pgf.gd.doc'.summary +local example = require 'pgf.gd.doc'.example + + + +-------------------------------------------------------------------- +key "MedianHeuristic" +summary "The median heuristic for 2-layer crossing minimization." +-------------------------------------------------------------------- + diff --git a/graphics/pgf/base/tex/generic/graphdrawing/lua/pgf/gd/doc/ogdf/layered/OptimalRanking.lua b/graphics/pgf/base/tex/generic/graphdrawing/lua/pgf/gd/doc/ogdf/layered/OptimalRanking.lua new file mode 100644 index 0000000000..25a8dd5262 --- /dev/null +++ b/graphics/pgf/base/tex/generic/graphdrawing/lua/pgf/gd/doc/ogdf/layered/OptimalRanking.lua @@ -0,0 +1,35 @@ +-- Copyright 2013 by Till Tantau +-- +-- This file may be distributed an/or modified +-- +-- 1. under the LaTeX Project Public License and/or +-- 2. under the GNU Public License +-- +-- See the file doc/generic/pgf/licenses/LICENSE for more information + +-- @release $Header$ + + +local key = require 'pgf.gd.doc'.key +local documentation = require 'pgf.gd.doc'.documentation +local summary = require 'pgf.gd.doc'.summary +local example = require 'pgf.gd.doc'.example + + +-------------------------------------------------------------------- +key "OptimalRanking" +summary "The optimal ranking algorithm." +documentation +[[ + The |OptimalRanking| implements the LP-based algorithm for + computing a node ranking with minimal edge lengths, which can + be used as first phase in |SugiyamaLayout|. +]] +-------------------------------------------------------------------- + + + +-------------------------------------------------------------------- +key "OptimalRanking.separateMultiEdges" +summary "If set to true, multi-edges will span at least two layers." +-------------------------------------------------------------------- diff --git a/graphics/pgf/base/tex/generic/graphdrawing/lua/pgf/gd/doc/ogdf/layered/SiftingHeuristic.lua b/graphics/pgf/base/tex/generic/graphdrawing/lua/pgf/gd/doc/ogdf/layered/SiftingHeuristic.lua new file mode 100644 index 0000000000..d0815a5b76 --- /dev/null +++ b/graphics/pgf/base/tex/generic/graphdrawing/lua/pgf/gd/doc/ogdf/layered/SiftingHeuristic.lua @@ -0,0 +1,34 @@ +-- Copyright 2013 by Till Tantau +-- +-- This file may be distributed an/or modified +-- +-- 1. under the LaTeX Project Public License and/or +-- 2. under the GNU Public License +-- +-- See the file doc/generic/pgf/licenses/LICENSE for more information + +-- @release $Header$ + + +local key = require 'pgf.gd.doc'.key +local documentation = require 'pgf.gd.doc'.documentation +local summary = require 'pgf.gd.doc'.summary +local example = require 'pgf.gd.doc'.example + + + +-------------------------------------------------------------------- +key "SiftingHeuristic" +summary "The sifting heuristic for 2-layer crossing minimization." +-------------------------------------------------------------------- + + +-------------------------------------------------------------------- +key "SiftingHeuristic.strategy" +summary "Sets a so-called ``sifting strategy''." +documentation +[[ + The following values are permissible: |left_to_right|, |desc_degree|, + and |random|. +]] +-------------------------------------------------------------------- diff --git a/graphics/pgf/base/tex/generic/graphdrawing/lua/pgf/gd/doc/ogdf/layered/SplitHeuristic.lua b/graphics/pgf/base/tex/generic/graphdrawing/lua/pgf/gd/doc/ogdf/layered/SplitHeuristic.lua new file mode 100644 index 0000000000..1579c7153d --- /dev/null +++ b/graphics/pgf/base/tex/generic/graphdrawing/lua/pgf/gd/doc/ogdf/layered/SplitHeuristic.lua @@ -0,0 +1,24 @@ +-- Copyright 2013 by Till Tantau +-- +-- This file may be distributed an/or modified +-- +-- 1. under the LaTeX Project Public License and/or +-- 2. under the GNU Public License +-- +-- See the file doc/generic/pgf/licenses/LICENSE for more information + +-- @release $Header$ + + +local key = require 'pgf.gd.doc'.key +local documentation = require 'pgf.gd.doc'.documentation +local summary = require 'pgf.gd.doc'.summary +local example = require 'pgf.gd.doc'.example + + + +-------------------------------------------------------------------- +key "SplitHeuristic" +summary "The split heuristic for 2-layer crossing minimization." +-------------------------------------------------------------------- + diff --git a/graphics/pgf/base/tex/generic/graphdrawing/lua/pgf/gd/doc/ogdf/layered/SugiyamaLayout.lua b/graphics/pgf/base/tex/generic/graphdrawing/lua/pgf/gd/doc/ogdf/layered/SugiyamaLayout.lua new file mode 100644 index 0000000000..ec75fa7e6e --- /dev/null +++ b/graphics/pgf/base/tex/generic/graphdrawing/lua/pgf/gd/doc/ogdf/layered/SugiyamaLayout.lua @@ -0,0 +1,113 @@ +-- Copyright 2013 by Till Tantau +-- +-- This file may be distributed an/or modified +-- +-- 1. under the LaTeX Project Public License and/or +-- 2. under the GNU Public License +-- +-- See the file doc/generic/pgf/licenses/LICENSE for more information + +-- @release $Header$ + + +local key = require 'pgf.gd.doc'.key +local documentation = require 'pgf.gd.doc'.documentation +local summary = require 'pgf.gd.doc'.summary +local example = require 'pgf.gd.doc'.example + + +-------------------------------------------------------------------------------- +key "SugiyamaLayout" +summary "The OGDF implementation of the Sugiyama algorithm." + +documentation [[ + This layout represents a customizable implementation of Sugiyama's + layout algorithm. The implementation used in |SugiyamaLayout| is based + on the following publications: + + \begin{itemize} + \item Emden R. Gansner, Eleftherios Koutsofios, Stephen + C. North, Kiem-Phong Vo: A technique for drawing directed + graphs. \emph{IEEE Trans. Software Eng.} 19(3):214--230, 1993. + \item Georg Sander: \emph{Layout of compound directed graphs.} + Technical Report, Universität des Saarlandes, 1996. + \end{itemize} +]] + +example +[[ +\tikz \graph [SugiyamaLayout] { a -- {b,c,d} -- e -- a }; +]] + +example +[[ +\tikz \graph [SugiyamaLayout, grow=right] { + a -- {b,c,d} -- e -- a +}; +]] + +example +[[ +\tikz [nodes={text height=.7em, text depth=.2em, + draw=black!20, thick, fill=white, font=\footnotesize}, + >={Stealth[round,sep]}, rounded corners, semithick] + \graph [SugiyamaLayout, FastSimpleHierarchyLayout, grow=-80, + level distance=1.5cm, sibling distance=7mm] { + "5th Edition" -> { "6th Edition", "PWB 1.0" }; + "6th Edition" -> { "LSX", "1 BSD", "Mini Unix", "Wollongong", "Interdata" }; + "Interdata" -> { "Unix/TS 3.0", "PWB 2.0", "7th Edition" }; + "7th Edition" -> { "8th Edition", "32V", "V7M", "Ultrix-11", "Xenix", "UniPlus+" }; + "V7M" -> "Ultrix-11"; + "8th Edition" -> "9th Edition"; + "1 BSD" -> "2 BSD" -> "2.8 BSD" -> { "Ultrix-11", "2.9 BSD" }; + "32V" -> "3 BSD" -> "4 BSD" -> "4.1 BSD" -> { "4.2 BSD", "2.8 BSD", "8th Edition" }; + "4.2 BSD" -> { "4.3 BSD", "Ultrix-32" }; + "PWB 1.0" -> { "PWB 1.2" -> "PWB 2.0", "USG 1.0" -> { "CB Unix 1", "USG 2.0" }}; + "CB Unix 1" -> "CB Unix 2" -> "CB Unix 3" -> { "Unix/TS++", "PDP-11 Sys V" }; + { "USG 2.0" -> "USG 3.0", "PWB 2.0", "Unix/TS 1.0" } -> "Unix/TS 3.0"; + { "Unix/TS++", "CB Unix 3", "Unix/TS 3.0" } -> + "TS 4.0" -> "System V.0" -> "System V.2" -> "System V.3"; + }; +]] +-------------------------------------------------------------------------------- + + + +-------------------------------------------------------------------------------- +key "SugiyamaLayout.runs" +summary "Determines, how many times the crossing minimization is repeated." +documentation +[[ +Each repetition (except for the first) starts with +randomly permuted nodes on each layer. Deterministic behavior can +be achieved by setting |SugiyamaLayout.runs| to 1. +]] +-------------------------------------------------------------------------------- + + + +-------------------------------------------------------------------------------- +key "SugiyamaLayout.transpose" +documentation [[ + Determines whether the transpose step is performed + after each 2-layer crossing minimization; this step tries to + reduce the number of crossings by switching neighbored nodes on + a layer. +]] +-------------------------------------------------------------------------------- + + + +-------------------------------------------------------------------------------- +key "SugiyamaLayout.fails" +documentation [[ + The number of times that the number of crossings may + not decrease after a complete top-down bottom-up traversal, + before a run is terminated. +]] +-------------------------------------------------------------------------------- + + +-- Local Variables: +-- mode:latex +-- End: diff --git a/graphics/pgf/base/tex/generic/graphdrawing/lua/pgf/gd/doc/ogdf/misclayout.lua b/graphics/pgf/base/tex/generic/graphdrawing/lua/pgf/gd/doc/ogdf/misclayout.lua new file mode 100644 index 0000000000..9f5f291461 --- /dev/null +++ b/graphics/pgf/base/tex/generic/graphdrawing/lua/pgf/gd/doc/ogdf/misclayout.lua @@ -0,0 +1,19 @@ +-- Copyright 2013 by Till Tantau +-- +-- This file may be distributed an/or modified +-- +-- 1. under the LaTeX Project Public License and/or +-- 2. under the GNU Public License +-- +-- See the file doc/generic/pgf/licenses/LICENSE for more information + +-- @release $Header$ + + + +--- +-- @section subsection {Miscellaneous Algorithms for Graph Drawing} +-- +-- + +local _
\ No newline at end of file diff --git a/graphics/pgf/base/tex/generic/graphdrawing/lua/pgf/gd/doc/ogdf/misclayout/BalloonLayout.lua b/graphics/pgf/base/tex/generic/graphdrawing/lua/pgf/gd/doc/ogdf/misclayout/BalloonLayout.lua new file mode 100644 index 0000000000..2a250ecece --- /dev/null +++ b/graphics/pgf/base/tex/generic/graphdrawing/lua/pgf/gd/doc/ogdf/misclayout/BalloonLayout.lua @@ -0,0 +1,48 @@ +-- Copyright 2013 by Till Tantau +-- +-- This file may be distributed an/or modified +-- +-- 1. under the LaTeX Project Public License and/or +-- 2. under the GNU Public License +-- +-- See the file doc/generic/pgf/licenses/LICENSE for more information + +-- @release $Header$ + + +local key = require 'pgf.gd.doc'.key +local documentation = require 'pgf.gd.doc'.documentation +local summary = require 'pgf.gd.doc'.summary +local example = require 'pgf.gd.doc'.example + + +-------------------------------------------------------------------------------- +key "BalloonLayout" +summary "A ``balloon layout''." + +documentation +[[ +This algorithm computes a radial (balloon) layout based on a +spanning tree. The algorithm is partially based on the paper +\emph{On Balloon Drawings of Rooted Trees} by Lin and Yen and on +\emph{Interacting with Huge Hierarchies: Beyond Cone Trees} by +Carriere and Kazman. +]] + +example +[[ +\tikz \graph [BalloonLayout] { a -- {b,c,d -- {e,f,h,h,i}, j -- k -- {l,m,n}} }; +]] +-------------------------------------------------------------------------------- + + + +-------------------------------------------------------------------------------- +key "BalloonLayout.evenAngles" +summary "Subtrees may be assigned even angles or angles depending on their size." +-------------------------------------------------------------------------------- + + +-- Local Variables: +-- mode:latex +-- End:
\ No newline at end of file diff --git a/graphics/pgf/base/tex/generic/graphdrawing/lua/pgf/gd/doc/ogdf/misclayout/CircularLayout.lua b/graphics/pgf/base/tex/generic/graphdrawing/lua/pgf/gd/doc/ogdf/misclayout/CircularLayout.lua new file mode 100644 index 0000000000..c5e2cf7a6c --- /dev/null +++ b/graphics/pgf/base/tex/generic/graphdrawing/lua/pgf/gd/doc/ogdf/misclayout/CircularLayout.lua @@ -0,0 +1,95 @@ +-- Copyright 2013 by Till Tantau +-- +-- This file may be distributed an/or modified +-- +-- 1. under the LaTeX Project Public License and/or +-- 2. under the GNU Public License +-- +-- See the file doc/generic/pgf/licenses/LICENSE for more information + +-- @release $Header$ + + +local key = require 'pgf.gd.doc'.key +local documentation = require 'pgf.gd.doc'.documentation +local summary = require 'pgf.gd.doc'.summary +local example = require 'pgf.gd.doc'.example + + +-------------------------------------------------------------------------------- +key "CircularLayout" +summary "The circular layout algorithm." + +documentation +[[ +The implementation used in CircularLayout is based on the following publication: +% +\begin{itemize} + \item Ugur Dogrus\"oz, Brendan Madden, Patrick Madden: Circular + Layout in the Graph Layout Toolkit. \emph{Proc. Graph Drawing 1996,} + LNCS 1190, pp. 92--100, 1997. +\end{itemize} +]] + +example +[[ +\tikz \graph [CircularLayout] { + a -- b -- c -- a -- d -- e -- f -- g -- d; + b -- {x,y,z}; +}; +]] +-------------------------------------------------------------------------------- + + + +-------------------------------------------------------------------------------- +key "CircularLayout.minDistCircle" +summary "The minimal padding between nodes on a circle." + +documentation "This is an alias for |part sep|." + +example +[[ +\tikz \graph [CircularLayout, part sep=1cm] { + a -- b -- c -- a -- d -- e -- f -- g -- d; + b -- {x,y,z}; +}; +]] +-------------------------------------------------------------------------------- + + +-------------------------------------------------------------------------------- +key "CircularLayout.minDistLevel" +summary "The minimal padding between nodes on different levels." + +documentation "This is an alias for |layer sep| and |level sep|." + +example +[[ +\tikz \graph [CircularLayout, layer sep=1cm] { + a -- b -- c -- a -- d -- e -- f -- g -- d; + b -- {x,y,z}; +}; +]] +-------------------------------------------------------------------------------- + + +-------------------------------------------------------------------------------- +key "CircularLayout.minDistSibling" +summary "The minimal padding between sibling nodes." + +documentation "This is an alias for |sibling sep|." + +example +[[ +\tikz \graph [CircularLayout, sibling sep=1cm] { + a -- b -- c -- a -- d -- e -- f -- g -- d; + b -- {x,y,z}; +}; +]] +-------------------------------------------------------------------------------- + + +-- Local Variables: +-- mode:latex +-- End:
\ No newline at end of file diff --git a/graphics/pgf/base/tex/generic/graphdrawing/lua/pgf/gd/doc/ogdf/module/AcyclicSubgraphModule.lua b/graphics/pgf/base/tex/generic/graphdrawing/lua/pgf/gd/doc/ogdf/module/AcyclicSubgraphModule.lua new file mode 100644 index 0000000000..b2bb7e1d24 --- /dev/null +++ b/graphics/pgf/base/tex/generic/graphdrawing/lua/pgf/gd/doc/ogdf/module/AcyclicSubgraphModule.lua @@ -0,0 +1,18 @@ +-- Copyright 2013 by Till Tantau +-- +-- This file may be distributed an/or modified +-- +-- 1. under the LaTeX Project Public License and/or +-- 2. under the GNU Public License +-- +-- See the file doc/generic/pgf/licenses/LICENSE for more information + +-- @release $Header$ + + +--- +-- @section subsubsection {Computing Acyclic Subgraphs Module} +-- + +local _ + diff --git a/graphics/pgf/base/tex/generic/graphdrawing/lua/pgf/gd/doc/ogdf/module/HierarchyLayoutModule.lua b/graphics/pgf/base/tex/generic/graphdrawing/lua/pgf/gd/doc/ogdf/module/HierarchyLayoutModule.lua new file mode 100644 index 0000000000..a0292530cd --- /dev/null +++ b/graphics/pgf/base/tex/generic/graphdrawing/lua/pgf/gd/doc/ogdf/module/HierarchyLayoutModule.lua @@ -0,0 +1,17 @@ +-- Copyright 2013 by Till Tantau +-- +-- This file may be distributed an/or modified +-- +-- 1. under the LaTeX Project Public License and/or +-- 2. under the GNU Public License +-- +-- See the file doc/generic/pgf/licenses/LICENSE for more information + +-- @release $Header$ + + +--- +-- @section subsubsection {The Hierarchy Layout Module} +-- + +local _ diff --git a/graphics/pgf/base/tex/generic/graphdrawing/lua/pgf/gd/doc/ogdf/module/InitialPlacer.lua b/graphics/pgf/base/tex/generic/graphdrawing/lua/pgf/gd/doc/ogdf/module/InitialPlacer.lua new file mode 100644 index 0000000000..328a2806db --- /dev/null +++ b/graphics/pgf/base/tex/generic/graphdrawing/lua/pgf/gd/doc/ogdf/module/InitialPlacer.lua @@ -0,0 +1,17 @@ +-- Copyright 2013 by Till Tantau +-- +-- This file may be distributed an/or modified +-- +-- 1. under the LaTeX Project Public License and/or +-- 2. under the GNU Public License +-- +-- See the file doc/generic/pgf/licenses/LICENSE for more information + +-- @release $Header$ + + +--- +-- @section subsubsection {The Initial Placer Module} +-- + +local _ diff --git a/graphics/pgf/base/tex/generic/graphdrawing/lua/pgf/gd/doc/ogdf/module/MultilevelBuilder.lua b/graphics/pgf/base/tex/generic/graphdrawing/lua/pgf/gd/doc/ogdf/module/MultilevelBuilder.lua new file mode 100644 index 0000000000..9604c98640 --- /dev/null +++ b/graphics/pgf/base/tex/generic/graphdrawing/lua/pgf/gd/doc/ogdf/module/MultilevelBuilder.lua @@ -0,0 +1,17 @@ +-- Copyright 2013 by Till Tantau +-- +-- This file may be distributed an/or modified +-- +-- 1. under the LaTeX Project Public License and/or +-- 2. under the GNU Public License +-- +-- See the file doc/generic/pgf/licenses/LICENSE for more information + +-- @release $Header$ + + +--- +-- @section subsubsection {The Multilevel Builder Module} +-- + +local _ diff --git a/graphics/pgf/base/tex/generic/graphdrawing/lua/pgf/gd/doc/ogdf/module/RankingModule.lua b/graphics/pgf/base/tex/generic/graphdrawing/lua/pgf/gd/doc/ogdf/module/RankingModule.lua new file mode 100644 index 0000000000..8dce27a335 --- /dev/null +++ b/graphics/pgf/base/tex/generic/graphdrawing/lua/pgf/gd/doc/ogdf/module/RankingModule.lua @@ -0,0 +1,17 @@ +-- Copyright 2013 by Till Tantau +-- +-- This file may be distributed an/or modified +-- +-- 1. under the LaTeX Project Public License and/or +-- 2. under the GNU Public License +-- +-- See the file doc/generic/pgf/licenses/LICENSE for more information + +-- @release $Header$ + + +--- +-- @section subsubsection {The Ranking Module} +-- + +local _ diff --git a/graphics/pgf/base/tex/generic/graphdrawing/lua/pgf/gd/doc/ogdf/module/TwoLayerCrossMin.lua b/graphics/pgf/base/tex/generic/graphdrawing/lua/pgf/gd/doc/ogdf/module/TwoLayerCrossMin.lua new file mode 100644 index 0000000000..877cba47f6 --- /dev/null +++ b/graphics/pgf/base/tex/generic/graphdrawing/lua/pgf/gd/doc/ogdf/module/TwoLayerCrossMin.lua @@ -0,0 +1,17 @@ +-- Copyright 2013 by Till Tantau +-- +-- This file may be distributed an/or modified +-- +-- 1. under the LaTeX Project Public License and/or +-- 2. under the GNU Public License +-- +-- See the file doc/generic/pgf/licenses/LICENSE for more information + +-- @release $Header$ + + +--- +-- @section subsubsection {The Two Layer Crossing Minimization Module} +-- + +local _ diff --git a/graphics/pgf/base/tex/generic/graphdrawing/lua/pgf/gd/doc/ogdf/planarity.lua b/graphics/pgf/base/tex/generic/graphdrawing/lua/pgf/gd/doc/ogdf/planarity.lua new file mode 100644 index 0000000000..fb1abb5ccd --- /dev/null +++ b/graphics/pgf/base/tex/generic/graphdrawing/lua/pgf/gd/doc/ogdf/planarity.lua @@ -0,0 +1,19 @@ +-- Copyright 2013 by Till Tantau +-- +-- This file may be distributed an/or modified +-- +-- 1. under the LaTeX Project Public License and/or +-- 2. under the GNU Public License +-- +-- See the file doc/generic/pgf/licenses/LICENSE for more information + +-- @release $Header$ + + + +--- +-- @section subsection {Algorithms for Drawing Planar Graphs} +-- +-- + +local _
\ No newline at end of file diff --git a/graphics/pgf/base/tex/generic/graphdrawing/lua/pgf/gd/doc/ogdf/planarity/PlanarizationLayout.lua b/graphics/pgf/base/tex/generic/graphdrawing/lua/pgf/gd/doc/ogdf/planarity/PlanarizationLayout.lua new file mode 100644 index 0000000000..e61f9281b9 --- /dev/null +++ b/graphics/pgf/base/tex/generic/graphdrawing/lua/pgf/gd/doc/ogdf/planarity/PlanarizationLayout.lua @@ -0,0 +1,66 @@ +-- Copyright 2013 by Till Tantau +-- +-- This file may be distributed an/or modified +-- +-- 1. under the LaTeX Project Public License and/or +-- 2. under the GNU Public License +-- +-- See the file doc/generic/pgf/licenses/LICENSE for more information + +-- @release $Header$ + + +local key = require 'pgf.gd.doc'.key +local documentation = require 'pgf.gd.doc'.documentation +local summary = require 'pgf.gd.doc'.summary +local example = require 'pgf.gd.doc'.example + + +-------------------------------------------------------------------------------- +key "PlanarizationLayout" +summary "The planarization layout algorithm." + +documentation +[[ + A |PlanarizationLayout| represents a customizable implementation + of the planarization approach for drawing graphs. The implementation + used in PlanarizationLayout is based on the following publication: + % + \begin{itemize} + \item C. Gutwenger, P. Mutzel: \emph{An Experimental Study of Crossing + Minimization Heuristics.} 11th International Symposium on Graph + Drawing 2003, Perugia (GD '03), LNCS 2912, pp. 13--24, 2004. + \end{itemize} +]] + +example +[[ +\tikz \graph [PlanarizationLayout] { a -- {b,c,d,e,f} -- g -- a }; +]] +-------------------------------------------------------------------------------- + + + +-------------------------------------------------------------------------------- +key "PlanarizationLayout.preprocessCliques" +summary "Configures, whether cliques are collapsed in a preprocessing step." +documentation +[[ + If set to true, a preprocessing for cliques (complete subgraphs) + is performed and cliques will be laid out in a special form (straight-line, + not orthogonal). The preprocessing may reduce running time and improve + layout quality if the input graphs contains dense subgraphs. +]] +-------------------------------------------------------------------------------- + + + +-------------------------------------------------------------------------------- +key "PlanarizationLayout.minCliqueSize" +summary "The minimum size of cliques collapsed in preprocessing." +-------------------------------------------------------------------------------- + + +-- Local Variables: +-- mode:latex +-- End:
\ No newline at end of file diff --git a/graphics/pgf/base/tex/generic/graphdrawing/lua/pgf/gd/examples.lua b/graphics/pgf/base/tex/generic/graphdrawing/lua/pgf/gd/examples.lua new file mode 100644 index 0000000000..de33c0ed37 --- /dev/null +++ b/graphics/pgf/base/tex/generic/graphdrawing/lua/pgf/gd/examples.lua @@ -0,0 +1,26 @@ +-- Copyright 2012 by Till Tantau +-- +-- This file may be distributed an/or modified +-- +-- 1. under the LaTeX Project Public License and/or +-- 2. under the GNU Public License +-- +-- See the file doc/generic/pgf/licenses/LICENSE for more information + +--- @release $Header$ + + + +-- Imports + +require "pgf" +require "pgf.gd" + + +-- Declare namespace +pgf.gd.examples = {} + + +-- Done + +return pgf.gd.examples
\ No newline at end of file diff --git a/graphics/pgf/base/tex/generic/graphdrawing/lua/pgf/gd/examples/ASCIIDisplayer.lua b/graphics/pgf/base/tex/generic/graphdrawing/lua/pgf/gd/examples/ASCIIDisplayer.lua new file mode 100644 index 0000000000..5acc28396b --- /dev/null +++ b/graphics/pgf/base/tex/generic/graphdrawing/lua/pgf/gd/examples/ASCIIDisplayer.lua @@ -0,0 +1,32 @@ +local InterfaceToDisplay = require "pgf.gd.interface.InterfaceToDisplay" + +InterfaceToDisplay.bind(require "pgf.gd.examples.BindingToASCII") +require "pgf.gd.layered.library" +require "pgf.gd.force.library" + +local algorithm = io.read():match("%s*graph%s*%[(.-)%]") + +InterfaceToDisplay.pushPhase(algorithm, "main", 1) +InterfaceToDisplay.pushOption("level distance", 6, 2) +InterfaceToDisplay.pushOption("sibling distance", 8, 3) +InterfaceToDisplay.beginGraphDrawingScope(3) +InterfaceToDisplay.pushLayout(4) + +for line in io.lines() do + if line:match("}") then + break + elseif line:find("-") then + local n1, dir, n2 = string.match(line, "^%s*(.-)%s*(-.)%s*(.-)%s*;") + InterfaceToDisplay.createEdge(n1, n2, dir, 4) + else + local n1 = string.match(line, "^%s*(.-)%s*;") + InterfaceToDisplay.createVertex(n1, "rectangle", nil, 4) + end +end + +InterfaceToDisplay.runGraphDrawingAlgorithm() +InterfaceToDisplay.renderGraph() +InterfaceToDisplay.endGraphDrawingScope() + + + diff --git a/graphics/pgf/base/tex/generic/graphdrawing/lua/pgf/gd/examples/BindingToASCII.lua b/graphics/pgf/base/tex/generic/graphdrawing/lua/pgf/gd/examples/BindingToASCII.lua new file mode 100644 index 0000000000..2b5c9cc0b6 --- /dev/null +++ b/graphics/pgf/base/tex/generic/graphdrawing/lua/pgf/gd/examples/BindingToASCII.lua @@ -0,0 +1,87 @@ +local lib = require "pgf.gd.lib" + +-- Create a binding to ourselves +local BindingToASCII = lib.class { base_class = require "pgf.gd.bindings.Binding" } + +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 + +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 + +function BindingToASCII:renderVertex(v) + canvas [math.floor(v.pos.x)][math.floor(v.pos.y)] = v.name +end + +function BindingToASCII:renderEdge(e) + + local function connect (p,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) + + -- Go upward with respect to x + if x2 < x1 then + x1, y1, x2, y2 = x2, y2, x1, y1 + end + + local delta_x = x2-x1 + local delta_y = y2-y1 + + if math.abs(delta_x) > math.abs(delta_y) then + 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 + elseif math.abs(delta_y) > 0 then + local slope = delta_x/delta_y + for i=y1,y2,(y1<y2 and 1) or -1 do + local x,y = math.floor(x1 + (i-y1)*slope + 0.5), i + + if canvas[x][y] == " " then + canvas[x][y] = '.' + end + end + end + end + + + local p = e.tail.pos + + for i=1,#e.path do + if type(e.path[i]) == "table" then + connect(p, e.path[i]) + p = e.path[i] + end + end + + connect(p, e.head.pos) +end + +return BindingToASCII diff --git a/graphics/pgf/base/tex/generic/graphdrawing/lua/pgf/gd/examples/SimpleDemo.lua b/graphics/pgf/base/tex/generic/graphdrawing/lua/pgf/gd/examples/SimpleDemo.lua new file mode 100644 index 0000000000..46eb4eff38 --- /dev/null +++ b/graphics/pgf/base/tex/generic/graphdrawing/lua/pgf/gd/examples/SimpleDemo.lua @@ -0,0 +1,82 @@ +-- Copyright 2010 by Renée Ahrens, Olof Frahm, Jens Kluttig, Matthias Schulz, Stephan Schuster +-- Copyright 2012 by Till Tantau +-- +-- This file may be distributed an/or modified +-- +-- 1. under the LaTeX Project Public License and/or +-- 2. under the GNU Public License +-- +-- See the file doc/generic/pgf/licenses/LICENSE for more information + +-- @release $Header$ + + +--- +-- @section subsubsection {The ``Hello World'' of Graph Drawing} +-- +-- @end + + +-- Inputs +local declare = require "pgf.gd.interface.InterfaceToAlgorithms".declare + +--- + +declare { + key = "simple demo layout", + algorithm = { + run = + function (self) + local g = self.digraph + local alpha = (2 * math.pi) / #g.vertices + + for i,vertex in ipairs(g.vertices) do + local radius = vertex.options['radius'] or g.options['radius'] + vertex.pos.x = radius * math.cos(i * alpha) + vertex.pos.y = radius * math.sin(i * alpha) + end + end + }, + + summary = [[" + This algorithm is the ``Hello World'' of graph drawing. + "]], + documentation = [=[" + The algorithm arranges nodes in a circle (without paying heed to the + sizes of the nodes or to the edges). In order to ``really'' layout + nodes in a circle, use |simple necklace layout|; the present layout + is only intended to demonstrate how much (or little) is needed to + implement a graph drawing algorithm. + % +\begin{codeexample}[code only, tikz syntax=false] +-- File pgf.gd.examples.SimpleDemo +local declare = require "pgf.gd.interface.InterfaceToAlgorithms".declare + +declare { + key = "simple demo layout", + algorithm = { + run = + function (self) + local g = self.digraph + local alpha = (2 * math.pi) / #g.vertices + + for i,vertex in ipairs(g.vertices) do + local radius = vertex.options['radius'] or g.options['radius'] + vertex.pos.x = radius * math.cos(i * alpha) + vertex.pos.y = radius * math.sin(i * alpha) + end + end + }, + summary = "This algorithm is the 'Hello World' of graph drawing.", + documentation = [[" + This algorithm arranges nodes in a circle ... + "]] +} +\end{codeexample} + + On the display layer (\tikzname, that is) the algorithm can now + immediately be employed; you just need to say + |\usegdlibrary{examples.SimpleDemo}| at the beginning + somewhere. + "]=] +}
\ No newline at end of file diff --git a/graphics/pgf/base/tex/generic/graphdrawing/lua/pgf/gd/examples/SimpleEdgeDemo.lua b/graphics/pgf/base/tex/generic/graphdrawing/lua/pgf/gd/examples/SimpleEdgeDemo.lua new file mode 100644 index 0000000000..3ff5f4ea23 --- /dev/null +++ b/graphics/pgf/base/tex/generic/graphdrawing/lua/pgf/gd/examples/SimpleEdgeDemo.lua @@ -0,0 +1,144 @@ +-- Copyright 2010 by Renée Ahrens, Olof Frahm, Jens Kluttig, Matthias Schulz, Stephan Schuster +-- Copyright 2012 by Till Tantau +-- +-- This file may be distributed an/or modified +-- +-- 1. under the LaTeX Project Public License and/or +-- 2. under the GNU Public License +-- +-- See the file doc/generic/pgf/licenses/LICENSE for more information + +-- @release $Header$ + + +--- +-- @section subsubsection {How To Generate Edges Inside an Algorithm} +-- +-- @end + + +-- Imports +local InterfaceToAlgorithms = require "pgf.gd.interface.InterfaceToAlgorithms" +local declare = InterfaceToAlgorithms.declare + +-- The class object +local SimpleEdgeDemo = {} + + +--- +declare { + key = "simple edge demo layout", + algorithm = SimpleEdgeDemo, + + summary = "This algorithm shows how edges can be created by an algorithm.", + documentation = [[" + For its job, the algorithm uses the function |createEdge|, which can be + called during the run of the algorithm to create edges in the + syntactic graph. The algorithm first does exactly the same as the + simple demo layout, then it creates an edge for every node where the + |new edge to| option is set. You will see in the code how this + option is declared and how we use it to look up a vertex in the + graph by its name. + % +\begin{codeexample}[preamble={ \usetikzlibrary{graphs,graphdrawing} + \usegdlibrary{examples}}] +\tikz [simple edge demo layout] +\graph [radius=2cm] { + a, b, c, d, e, f; + + e -> [red] f; % normal edge + + % Edges generated by the algorithm: + a[new edge to=b]; + b[new edge to=d]; + c[new edge to=f]; +}; +\end{codeexample} + + And the algorithm: + % +\begin{codeexample}[code only, tikz syntax=false] + -- File pgf.gd.examples.SimpleEdgeDemo + + -- Imports + local InterfaceToAlgorithms = require "pgf.gd.interface.InterfaceToAlgorithms" + local declare = InterfaceToAlgorithms.declare + + -- The class object + local SimpleEdgeDemo = {} + +declare { + key = "simple edge demo layout", + algorithm = SimpleEdgeDemo, + summary = "This algorithm shows...", +} +\end{codeexample} + + Next comes the declaration of the new option |new edge to|: + % +\begin{codeexample}[code only, tikz syntax=false] +declare { + key = "new edge to", + type = "string", + summary = "This option takes the name of a vertex..." +} +\end{codeexample} + + Finally, the algorithm's code: + % +\begin{codeexample}[code only, tikz syntax=false] +function SimpleEdgeDemo:run() + -- As in a SimpleDemo: + ... + -- Now add some edges: + for _,tail in ipairs(g.vertices) do + local name = tail.options['new edge to'] + if name then + local node = InterfaceToAlgorithms.findVertexByName(name) + if node and self.digraph:contains(node) then + InterfaceToAlgorithms.createEdge (self, tail, node) + end + end + end +end +\end{codeexample} + "]] +} + +--- +declare { + key = "new edge to", + type = "string", + + summary = [[" + This option takes the name of a vertex. An edge leading to this + vertex is added to the syntactic digraph. + "]] +} + + +function SimpleEdgeDemo:run() + + -- As in a SimpleDemo: + local g = self.digraph + local alpha = (2 * math.pi) / #g.vertices + + for i,vertex in ipairs(g.vertices) do + local radius = vertex.options['radius'] or g.options['radius'] + vertex.pos.x = radius * math.cos(i * alpha) + vertex.pos.y = radius * math.sin(i * alpha) + end + + -- Now add some edges: + for _,tail in ipairs(g.vertices) do + local name = tail.options['new edge to'] + if name then + local node = InterfaceToAlgorithms.findVertexByName(name) + if node and self.digraph:contains(node) then + InterfaceToAlgorithms.createEdge (self, tail, node) + end + end + end +end + +return SimpleEdgeDemo diff --git a/graphics/pgf/base/tex/generic/graphdrawing/lua/pgf/gd/examples/SimpleHuffman.lua b/graphics/pgf/base/tex/generic/graphdrawing/lua/pgf/gd/examples/SimpleHuffman.lua new file mode 100644 index 0000000000..6aa9bffb72 --- /dev/null +++ b/graphics/pgf/base/tex/generic/graphdrawing/lua/pgf/gd/examples/SimpleHuffman.lua @@ -0,0 +1,287 @@ +-- Copyright 2012 by Till Tantau +-- +-- This file may be distributed an/or modified +-- +-- 1. under the LaTeX Project Public License and/or +-- 2. under the GNU Public License +-- +-- See the file doc/generic/pgf/licenses/LICENSE for more information + +-- @release $Header$ + + +--- +-- @section subsubsection {How To Generate Nodes Inside an Algorithm} +-- +-- @end + + + +-- Imports +local layered = require "pgf.gd.layered" +local InterfaceToAlgorithms = require "pgf.gd.interface.InterfaceToAlgorithms" +local declare = require "pgf.gd.interface.InterfaceToAlgorithms".declare + +-- The class +local SimpleHuffman = {} + + +--- + +declare { + key = "simple Huffman layout", + algorithm = SimpleHuffman, + + postconditions = { + upward_oriented = true + }, + + summary = [[" + This algorithm demonstrates how an algorithm can generate new nodes. + "]], + documentation = [[" + The input graph should just consist of some nodes (without + edges) and each node should have a |probability| key set. The nodes + will then be arranged in a line (as siblings) and a Huffman tree + will be constructed ``above'' these nodes. For the construction of + the Huffman tree, new nodes are created and connected. + + \pgfgdset{ + HuffmanLabel/.style={/tikz/edge node={node[fill=white,font=\footnotesize,inner sep=1pt]{#1}}}, + HuffmanNode/.style={/tikz/.cd,circle,inner sep=0pt,outer sep=0pt,draw,minimum size=3pt} + } + +\begin{codeexample}[preamble={ \usetikzlibrary{graphs,graphdrawing,quotes} + \usegdlibrary{examples}}] +\tikz \graph [simple Huffman layout, + level distance=7mm, sibling distance=8mm, grow'=up] +{ + a ["0.5", probability=0.5], + b ["0.12", probability=0.12], + c ["0.2", probability=0.2], + d ["0.1", probability=0.1], + e ["0.11", probability=0.11] +}; +\end{codeexample} + % + The file starts with some setups and declarations: + % +\begin{codeexample}[code only, tikz syntax=false] +-- File pgf.gd.examples.SimpleHuffman + +local declare = require "pgf.gd.interface.InterfaceToAlgorithms".declare + +-- The class +local SimpleHuffman = {} + +declare { + key = "simple Huffman layout", + algorithm = SimpleHuffman, + postconditions = { upward_oriented = true } + summary = "..." +} + +declare { + key = "probability", + type = "number", + initial = "1", + summary = "..." +} + +-- Import +local layered = require "pgf.gd.layered" +local InterfaceToAlgorithms = require "pgf.gd.interface.InterfaceToAlgorithms" +local Storage = require "pgf.gd.lib.Storage" + +local probability = Storage.new() +local layer = Storage.new() + +function SimpleHuffman:run() + -- Construct a Huffman tree on top of the vertices... +\end{codeexample} + + Next comes a setup, where we create the working list of vertices + that changes as the Huffman coding method proceeds: + % +\begin{codeexample}[code only, tikz syntax=false] + -- Shorthand + local function prop (v) + return probability[v] or v.options['probability'] + end + + -- Copy the vertex table, since we are going to modify it: + local vertices = {} + for i,v in ipairs(self.ugraph.vertices) do + vertices[i] = v + end +\end{codeexample} + + The initial vertices are arranged in a line on the last layer. The + function |ideal_sibling_distance| takes care of the rather + complicated handling of the (possibly rotated) bounding boxes and + separations. The |props| and |layer| are tables used by + algorithms to ``store stuff'' at a vertex or at an arc. The + table will be accessed by |arrange_layers_by_baselines| to + determine the ideal vertical placements. + % +\begin{codeexample}[code only, tikz syntax=false] + -- Now, arrange the nodes in a line: + vertices [1].pos.x = 0 + layer[ vertices [1] ] = #vertices + for i=2,#vertices do + local d = layered.ideal_sibling_distance(self.adjusted_bb, self.ugraph, vertices[i-1], vertices[i]) + vertices [i].pos.x = vertices[i-1].pos.x + d + layer[ vertices [i] ] = #vertices + end +\end{codeexample} + + Now comes the actual Huffman algorithm: Always find the vertices + with a minimal probability\dots + % +\begin{codeexample}[code only, tikz syntax=false] + -- Now, do the Huffman thing... + while #vertices > 1 do + -- Find two minimum probabilities + local min1, min2 + + for i=1,#vertices do + if not min1 or prop(vertices[i]) < prop(vertices[min1]) then + min2 = min1 + min1 = i + elseif not min2 or prop(vertices[i]) < prop(vertices[min2]) then + min2 = i + end + end +\end{codeexample} + % + \dots and connect them with a new node. This new node gets the + option |HuffmanNode|. It is now the job of the higher layers to map + this option to something ``nice''. + % +\begin{codeexample}[code only, tikz syntax=false] + -- Create new node: + local p = prop(vertices[min1]) + prop(vertices[min2]) + local v = InterfaceToAlgorithms.createVertex(self, { generated_options = {{key="HuffmanNode"}}}) + probability[v] = p + layer[v] = #vertices-1 + v.pos.x = (vertices[min1].pos.x + vertices[min2].pos.x)/2 + vertices[#vertices + 1] = v + + InterfaceToAlgorithms.createEdge (self, v, vertices[min1], + {generated_options = {{key="HuffmanLabel", value = "0"}}}) + InterfaceToAlgorithms.createEdge (self, v, vertices[min2], + {generated_options = {{key="HuffmanLabel", value = "1"}}}) + + table.remove(vertices, math.max(min1, min2)) + table.remove(vertices, math.min(min1, min2)) + end +\end{codeexample} + % + Ok, we are mainly done now. Finish by computing vertical placements + and do formal cleanup. + % +\begin{codeexample}[code only, tikz syntax=false] + layered.arrange_layers_by_baselines(layers, self.adjusted_bb, self.ugraph) +end +\end{codeexample} + + In order to use the class, we have to make sure that, on the + display layer, the options |HuffmanLabel| and |HuffmanNode| are + defined. This is done by adding, for instance, the following to + \tikzname: + % +\begin{codeexample}[code only] +\pgfkeys{ + /graph drawing/HuffmanLabel/.style={ + /tikz/edge node={node[fill=white,font=\footnotesize,inner sep=1pt]{#1}} + }, + /graph drawing/HuffmanNode/.style={ + /tikz/.cd,circle,inner sep=0pt,outer sep=0pt,draw,minimum size=3pt + } +} +\end{codeexample} + "]] +} + + +--- + +declare { + key = "probability", + type = "number", + initial = "1", + + summary = [[" + The probability parameter. It is used by the Huffman algorithm to + group nodes. + "]] +} + +-- Imports + +local Storage = require 'pgf.gd.lib.Storage' + +-- Storages + +local probability = Storage.new() +local layer = Storage.new() + + +function SimpleHuffman:run() + -- Construct a Huffman tree on top of the vertices... + + -- Shorthand + local function prop (v) + return probability[v] or v.options['probability'] + end + + -- Copy the vertex table, since we are going to modify it: + local vertices = {} + for i,v in ipairs(self.ugraph.vertices) do + vertices[i] = v + end + + -- Now, arrange the nodes in a line: + vertices [1].pos.x = 0 + layer[vertices [1]] = #vertices + for i=2,#vertices do + local d = layered.ideal_sibling_distance(self.adjusted_bb, self.ugraph, vertices[i-1], vertices[i]) + vertices [i].pos.x = vertices[i-1].pos.x + d + layer[vertices [i]] = #vertices + end + + -- Now, do the Huffman thing... + while #vertices > 1 do + -- Find two minimum probabilities + local min1, min2 + + for i=1,#vertices do + if not min1 or prop(vertices[i]) < prop(vertices[min1]) then + min2 = min1 + min1 = i + elseif not min2 or prop(vertices[i]) < prop(vertices[min2]) then + min2 = i + end + end + + -- Create new node: + local p = prop(vertices[min1]) + prop(vertices[min2]) + local v = InterfaceToAlgorithms.createVertex(self, { generated_options = {{key="HuffmanNode"}}}) + probability[v] = p + layer[v] = #vertices-1 + v.pos.x = (vertices[min1].pos.x + vertices[min2].pos.x)/2 + vertices[#vertices + 1] = v + + InterfaceToAlgorithms.createEdge (self, v, vertices[min1], + {generated_options = {{key="HuffmanLabel", value = "0"}}}) + InterfaceToAlgorithms.createEdge (self, v, vertices[min2], + {generated_options = {{key="HuffmanLabel", value = "1"}}}) + + table.remove(vertices, math.max(min1, min2)) + table.remove(vertices, math.min(min1, min2)) + end + + layered.arrange_layers_by_baselines(layer, self.adjusted_bb, self.ugraph) +end + +return SimpleHuffman diff --git a/graphics/pgf/base/tex/generic/graphdrawing/lua/pgf/gd/examples/example_graph_for_ascii_displayer.txt b/graphics/pgf/base/tex/generic/graphdrawing/lua/pgf/gd/examples/example_graph_for_ascii_displayer.txt new file mode 100644 index 0000000000..052ebdcdb8 --- /dev/null +++ b/graphics/pgf/base/tex/generic/graphdrawing/lua/pgf/gd/examples/example_graph_for_ascii_displayer.txt @@ -0,0 +1,20 @@ +graph [layered layout] { + Alice; + Bob; + Charly; + Dave; + Eve; + Fritz; + George; + Alice -> Bob; + Alice -> Charly; + Charly -> Dave; + Bob -> Dave; + Dave -> Eve; + Eve -> Fritz; + Fritz -> Alice; + George -> Eve; + George -> Fritz; + Alice -> George; +} + diff --git a/graphics/pgf/base/tex/generic/graphdrawing/lua/pgf/gd/examples/library.lua b/graphics/pgf/base/tex/generic/graphdrawing/lua/pgf/gd/examples/library.lua new file mode 100644 index 0000000000..0c977600a7 --- /dev/null +++ b/graphics/pgf/base/tex/generic/graphdrawing/lua/pgf/gd/examples/library.lua @@ -0,0 +1,30 @@ +-- Copyright 2012 by Till Tantau +-- +-- This file may be distributed an/or modified +-- +-- 1. under the LaTeX Project Public License and/or +-- 2. under the GNU Public License +-- +-- See the file doc/generic/pgf/licenses/LICENSE for more information + +-- @release $Header$ + + + +--- +-- This package presents some examples of how different aspects of the +-- graph drawing engine can be used. In particular, the algorithms of +-- this package are not really meant to be used to layout graphs +-- (although they can be used, in principle); rather you are invited +-- to have a look at their implementation and to adapt them to your needs. +-- +-- @library + +local examples + + +-- Load algorithms from: +require "pgf.gd.examples.SimpleDemo" +require "pgf.gd.examples.SimpleEdgeDemo" +require "pgf.gd.examples.SimpleHuffman" + diff --git a/graphics/pgf/base/tex/generic/graphdrawing/lua/pgf/gd/experimental/evolving/GraphAnimationCoordination.lua b/graphics/pgf/base/tex/generic/graphdrawing/lua/pgf/gd/experimental/evolving/GraphAnimationCoordination.lua new file mode 100644 index 0000000000..31f8f27431 --- /dev/null +++ b/graphics/pgf/base/tex/generic/graphdrawing/lua/pgf/gd/experimental/evolving/GraphAnimationCoordination.lua @@ -0,0 +1,638 @@ +-- Copyright 2015 by Malte Skambath +-- +-- This file may be distributed an/or modified +-- +-- 1. under the LaTeX Project Public License and/or +-- 2. under the GNU Public License +-- +-- See the file doc/generic/pgf/licenses/LICENSE for more information + + +-- +-- +-- @field.visible_objects An array which stores for each supernode a mapping +-- of snapshots to the related visible snapshot nodes. +-- Note that these mappings may differ from the supergraph +-- because if there are two snapshot nodes in consecutive snapshots +-- then the first can be shown for a longer time period to +-- put aside some fade animations. +-- @field is_first A table storing for each snapshot node or snapshot arc if it +-- appears in its snapshot. This means that in the previous snapshot +-- there is no corresponding arc or node. +-- @field is_last A table storing for each snapshot node or arc if there +-- is no representative in the next snapshot. +-- @field move_on_enter A table which stores for each snapshot object if it is in +-- motion while it appears in its snapshot. +-- @field move_on_leave A table which stores for each snapshot object if it is in +-- motion while switching to the next snapshot +-- @field last_rep +-- A table which stores for every snapshot node if the representing (visible) node +-- disappears with the next snapshot. +-- +-- @field previous_node The same as |next_node| just for the previous node +-- @field next_node A Storage to map each snapshot node to the next node in the +-- following snapshot related to the same supernode. +-- If in the next snapshot there is node following snapshot node +-- then the value is nil. +-- +local GraphAnimationCoordination = {} + +-- Imports +local lib = require "pgf.gd.lib" +local declare = require("pgf.gd.interface.InterfaceToAlgorithms").declare +local Storage = require "pgf.gd.lib.Storage" +local Coordinate = require "pgf.gd.model.Coordinate" + + +declare { + key = "modified", + type = "boolean", + Initial = false, + documentation = [[" + This key specifies, if a supernode changed its + visual properties since the last snapshot. + The default value is |false| and prevent the algorithm + to produce a lot of unnecessary animations. + "]] +} + +declare { + key = "unmodified", + use = { + { key = "modified", boolean = false}, + }, +} + + +--- +declare { + key = "minimum rest time", + type = "number", + initial = 0.5, + documentation = [[" + This key specifies a minimum time in which a single node + has to be prohibited to be animated. + For a node with minimum rest time of 1s that exists in a snapshot + at time $t$ this means that all animations including movements and fadings + are only allowed before $t$-0.5s and after $t$+0.5s. + "]], +} + +declare { + key = "maximum motion time", + type = "number", + initial = math.huge, + documentation = [[" + Use this key if you want to limit the time during nodes are allowed to move + when they changing their positions. + "]], +} + +declare { + key = "overlapping transition", + type = "boolean", + initial = true, + documentation = [[" + Use this key if you want to allow that the fade animations for or + disappearing nodes may occurs while the mid time between two snapshots. + If false then the appearing ends on the midtime and the disappearing + starts in this moment. + "]] +} + +--- +declare { + key = "default evolving graph animation", + algorithm = GraphAnimationCoordination, + phase = "evolving graph animation", + phase_default = true, + summary = [[" + This phase animates all vertices including movements and + fade in or fade out animations given an evolving graph as sequence + of single snapshot graphs. + "]], + documentation = [[" + This phase animates all vertices including movements and + fade in or fade out animations given an evolving graph as sequence + of single snapshot graphs. + + Your algorithm needs to work on evolving graphs and has to use + the |evolving graph animation| phase. You do not need to use + this key by yourself then because this key starts the default + algorithm algorithm of the phase. + % + \begin{codeexample}[] + local ga_class = self.digraph.options.algorithm_phases['evolving graph animation'] + -- animate graph + ga_class.new { + main_algorithm = self, + supergraph = supergraph, + digraph = self.digraph, + ugraph = self.ugraph + }:run() + \end{codeexample} + + This algorithm and phase require a supergraph instance and the original + digraph and ugraph. Note that you have to set the layout of the snapshot + nodes before running algorithms of this is useful. + "]], +} + +-- Help functions + +-- +-- Appends a move animation to a given snapshot object such that the +-- object moves from one point to another on a straight line. Note +-- that the coordinates of the two points are given as relative +-- coordinates to the current origin of the object. +-- +-- This means if we want to move a node 1cm to the right the value of +-- |c_from| has to be (0,0) while |c_to| must be (1,0). The argument +-- |c_from| is useful for a node which has a position but its +-- previous node related to the same supervertex is at a different +-- position. Then we can use this argument to move the new node to +-- its origin position for smooth transitions. +-- +-- @field object The snapshot object which should be moved +-- +-- @field c_from The coordinate where the animation starts +-- +-- @field c_to The coordinate where the animation should end +-- +-- @field t_start The time when the movement starts. +-- +-- @field t_end The time when the animation stops. +local function append_move_animation(object, c_from, c_to, t_start, t_end) + if not object then return end + assert(object, "no object to animate") + if ((c_from.x~=c_to.x) or (c_from.y~=c_to.y))then + local animations = object.animations or {} + local c1 = Coordinate.new((2*c_from.x+c_to.x)/3,(2*c_from.y+c_to.y)/3) + local c2 = Coordinate.new((c_from.x+2*c_to.x)/3,(c_from.y+2*c_to.y)/3) + local t1 = (7*t_start + 5*t_end)/12 + local t2 = (5*t_start + 7*t_end)/12 + table.insert(animations, { + attribute = "translate", + entries = { + { t = t_start, value = c_from}, +-- { t = t1, value = c1 }, +-- { t = t2, value = c2 }, + { t = t_end, value = c_to } + }, + options = { { key = "freeze at end", }, +-- {key = "entry control", value="0}{1",} + } + }) + object.animations = animations + end +end + +local function append_fade_animation(object, v_start, v_end, t_start, t_end) + local animations = object.animations or {} + + if v_start == 0 then + table.insert(animations, { + attribute = "stage", + entries = { { t = t_start, value = "true"}, }, + options = { { key = "freeze at end" } } + }) + elseif v_end == 0 and nil then + table.insert(animations, { + attribute = "stage", + entries = { { t = t_end, value = "false"}, }, + options = { --{ key = "freeze at end" } + } + }) + end + + table.insert(animations, { + attribute = "opacity", + entries = { + { t = t_start, value = v_start }, + { t = t_end, value = v_end } }, + options = { { key = "freeze at end" } } + }) + object.animations = animations +end + +-- +-- check if the difference/vector between two pairs (a1,a2),(b1,b2) of points +-- is the same. +local function eq_offset(a1, a2, b1, b2) + local dx = ((a1.x-a2.x) - (b1.x-b2.x)) + local dy = ((a1.y-a2.y) - (b1.y-b2.y)) + if dx<0 then dx = -dx end + if dy<0 then dy = -dy end + return dx<0.001 and dy<0.001 +end + +-- +-- Check if two arcs connect a pair of nodes at the same position. +-- This can be used as an indicator that two consecutive arcs +-- can be represented by the same arc object. +-- +local function eq_arc(arc1, arc2) + if not arc1 or not arc2 then + return false + end + return eq_offset(arc1.tail.pos, arc1.head.pos, arc2.tail.pos, arc2.head.pos) +end + + +-- Implementation + +function GraphAnimationCoordination:run() + assert(self.supergraph, "no supergraph defined") + + self.is_first = Storage.new() + self.is_last = Storage.new() + self.last_rep = Storage.new() + self.move_on_enter = Storage.new() + self.move_on_leave = Storage.new() + self.previous_node = Storage.new() + self.next_node = Storage.new() + self.visible_objects = Storage.new() + + + self:precomputeNodes() + self:precomputeEdges() + self:animateNodeAppearing() + self:animateEdgeAppearing() + self:generateNodeMotions() + self:generateEdgeMotions() +end + +function GraphAnimationCoordination:generateNodeMotions(node_types) + local supergraph = self.supergraph + local graph = self.digraph + + for _, supervertex in ipairs(self.supergraph.vertices) do + local lj = -1 + local last_v = nil + local last_time = nil + for j, s in ipairs(supergraph.snapshots) do + local vertex = supergraph:getSnapshotVertex(supervertex, s) + + if lj == j-1 and vertex and last_v then + local mrt1 = last_v.options["minimum rest time"]/2 + local mrt2 = vertex.options["minimum rest time"]/2 + + local s1 = Coordinate.new(0,0) + local e1 = Coordinate.new(vertex.pos.x-last_v.pos.x,-vertex.pos.y+last_v.pos.y) + + local s2 = Coordinate.new(-vertex.pos.x+last_v.pos.x,vertex.pos.y-last_v.pos.y) + local e2 = Coordinate.new(0,0) + + local t_end = s.timestamp - math.max(0, mrt2) + local t_start = last_time + math.max(0,mrt1) + + local representative = self.visible_objects[supervertex][s] + if representative == vertex then + append_move_animation(vertex, s2, e2, t_start, t_end) + append_move_animation(last_v, s1, e1, t_start, t_end) + else + append_move_animation(representative,s1,e1,t_start,t_end) + end + end + last_time = s.timestamp + lj = j + last_v = vertex + end + end +end + + + + + +function GraphAnimationCoordination:generateEdgeMotions() + local supergraph = self.supergraph + local graph = self.digraph + + for i, arc in ipairs(supergraph.arcs) do + local head = arc.head + local tail = arc.tail + + local last_arc = nil + local last_time = nil + local last_v = nil + local last_w = nil + + for j, s in ipairs(supergraph.snapshots) do + local v = supergraph:getSnapshotVertex(tail,s) + local w = supergraph:getSnapshotVertex(head,s) + + if v and w then + local this_arc = graph:arc(v,w) --or graph:arc(w,v) + if this_arc then + if this_arc and last_arc then + local mrt1 = last_v.options["minimum rest time"]/2 + local mrt2 = v.options["minimum rest time"]/2 + + local s1 = Coordinate.new(0,0)--lv.pos + local e1 = Coordinate.new(v.pos.x-last_v.pos.x,-v.pos.y+last_v.pos.y) + + local s2 = Coordinate.new(-v.pos.x+last_v.pos.x,v.pos.y-last_v.pos.y) + local e2 = Coordinate.new(0,0) + + local t_end = s.timestamp - math.max(0,mrt2) + local t_start = last_time + math.max(0,mrt1) + + local representative = self.visible_objects[arc][s] + if representative == this_arc then + append_move_animation(last_arc, s1, e1, t_start,t_end) + append_move_animation(this_arc, s2, e2, t_start,t_end) + else + append_move_animation(representative,s1,e1,t_start,t_end) + end + this_arc = representative + end + last_arc = this_arc + last_v = v + last_time = s.timestamp + else + last_arc = nil + end + else + last_arc = nil + end + end + end +end + +-- +-- +-- @field t_transition The mid time between two snapshot times. +-- @field fade_duration The duration of the fade animation +-- @field overlapping A boolean defining if the animation occurs +-- before and after the mid time (true) or if it +-- starts/end only in one interval (false) +-- @field closing A boolean specifying if this is an outfading time +local function compute_fade_times(t_transition, fade_duration, overlapping, closing) + + if overlapping then + t_start = t_transition - fade_duration / 2 + t_end = t_transition + fade_duration / 2 + else + if closing then + t_start = t_transition - fade_duration + t_end = t_transition + else + t_start = t_transition + t_end = t_transition + fade_duration + end + end + return {t_start = t_start, t_end = t_end} +end + +function GraphAnimationCoordination:animateNodeAppearing() + local supergraph = self.supergraph + for i,vertex in ipairs(self.ugraph.vertices) do + local snapshot = supergraph:getSnapshot(vertex) + local interval = snapshot.interval + local supernode = supergraph:getSupervertex(vertex) + local representative = self.visible_objects[supernode][snapshot] + local overlapping_in = true -- init true for crossfading + local overlapping_out= true + local minimum_rest_time = math.max(0,vertex.options["minimum rest time"]) + local allow_overlapping = vertex.options["overlapping transition"] + local fadein_duration = 0.01 + local fadeout_duration = 0.01 + + if self.is_first[vertex] then + fadein_duration = self.ugraph.options["fadein time"] + overlapping_in = false or allow_overlapping + end + if self.is_last[vertex] then + fadeout_duration = self.ugraph.options["fadeout time"] + overlapping_out = false or allow_overlapping + end + + if fadein_duration == math.huge or fadein_duration<0 then + fadein_duration = (interval.to-interval.from-minimum_rest_time)/2 + if overlapping then fadein_duration = fadein_duration * 2 end + end + if fadeout_duration == math.huge or fadeout_duration<0 then + fadeout_duration = (interval.to-interval.from-minimum_rest_time)/2 + if overlapping then fadeout_duration = fadeout_duration*2 end + end + + local fin = compute_fade_times(interval.from, fadein_duration, overlapping_in, false) + local fout = compute_fade_times(interval.to, fadeout_duration, overlapping_out, true) + + vertex.animations = vertex.animations or {} + + if representative~= vertex then + table.insert(vertex.animations,{ + attribute = "stage", + entries = { { t = 0, value = "false"}, }, + options = {} + }) + end + + if interval.from > -math.huge and (vertex == representative or self.is_first[vertex]) then + -- only appears if the snapshot node is its own repr. or if in the prev snapshot is + -- no representative. + append_fade_animation(representative, 0, 1, fin.t_start, fin.t_end) + end + if interval.to < math.huge and (self.is_last[vertex] or self.last_rep[vertex]) then + -- The snapshot node only disappears when the node is not visible + -- in the next or (this=)last snapshot: + append_fade_animation(representative, 1, 0, fout.t_start, fout.t_end) + end + end +end + + + +function GraphAnimationCoordination:animateEdgeAppearing() + local supergraph = self.supergraph + local graph = self.digraph + for _,edge in ipairs(graph.arcs) do + local snapshot = supergraph:getSnapshot(edge.head) + local int = snapshot.interval + local superarc = supergraph:getSuperarc(edge) + local representative = self.visible_objects[superarc][snapshot] or edge + + local minimum_rest_time = math.max(0,edge.head.options["minimum rest time"]/2, + edge.tail.options["minimum rest time"]/2) + + local appears = math.max(int.from, int.from) + local disappears = math.min(int.to, int.to) + + local overlapping_in = true -- init true for crossfading + local overlapping_out= true + local fadein_duration = 0.01 + local fadeout_duration = 0.01 + local allow_overlapping = (edge.tail.options["overlapping transition"] and edge.head.options["overlapping transition"]) + + if self.is_first[edge] and not self.move_on_enter[edge] and not self.move_on_enter[edge.head] then + fadein_duration = self.ugraph.options["fadein time"] + overlapping_in = false or allow_overlapping + end + + if self.is_last[edge] and not self.move_on_leave[edge] then + fadeout_duration = self.ugraph.options["fadeout time"] + overlapping_out = false or allow_overlapping + end + + + if self.is_first[edge] + and (self.move_on_enter[edge.head] + or self.move_on_enter[edge.tail] ) + then + appears = snapshot.timestamp - minimum_rest_time + end + if self.is_last[edge] and + (self.move_on_leave[edge.head] + or self.move_on_leave[edge.tail] + ) then + disappears = snapshot.timestamp + minimum_rest_time + end + + local fin = compute_fade_times(appears, fadein_duration, overlapping_in,false) + local fout = compute_fade_times(disappears,fadeout_duration,overlapping_out,true) + + edge.animations = edge.animations or {} + + if representative~=edge then + table.insert(edge.animations,{ + attribute = "stage", + entries = { { t = 0, value = "false"}, }, + options = {}}) + end + + -- Fade in: + if appears > -math.huge and (edge == representative or self.is_first[edge]) then + append_fade_animation(representative, 0, 1, fin.t_start, fin.t_end ) + end + + -- Fade out: + if disappears < math.huge and (self.is_last[edge] or self.last_rep[edge])then + append_fade_animation(representative, 1, 0, fout.t_start,fout.t_end ) + end + end +end + +function GraphAnimationCoordination:precomputeNodes() + local supergraph = self.supergraph + + for _, supernode in ipairs(supergraph.vertices) do + + local vis_nodes = {} + self.visible_objects[supernode] = vis_nodes + + local any_previous_node = nil + local previous_representant = nil + local node_before = nil + + for i, s in ipairs(supergraph.snapshots) do + local node = supergraph:getSnapshotVertex(supernode, s) + + if node then + -- assume the node is the last node + self.is_last[node] = true + + if node.options["modified"] then + -- modified + vis_nodes[s] = node + previous_representant = node + if any_previous_node then + self.last_rep[any_previous_node] = true + end + else + -- unmodified + previous_representant = previous_representant or node + vis_nodes[s] = previous_representant + end + any_previous_node = node + + if node_before then + self.is_last[node_before] = false + self.previous_node[node] = node_before + self.next_node[node_before] = node + + local do_move = (( node.pos.x ~= node_before.pos.x ) + or (node.pos.y ~= node_before.pos.y)) + self.move_on_enter[node] = do_move + self.move_on_leave[node_before] = do_move + else + self.is_first[node] = true + end + node_before = node + else + node_before = nil + end + end + end +end + +function GraphAnimationCoordination:precomputeEdges() + -- 1. classify arcs (appearing, disappearing) + for _, arc in ipairs(self.digraph.arcs) do + local head = arc.head + local tail = arc.tail + if not ( self.is_first[head] or self.is_first[tail]) then + if not self.digraph:arc(self.previous_node[tail], self.previous_node[head]) then + -- new arc connects existing nodes + self.is_first[arc] = true + end + else + -- arc and at least one node is new. + self.is_first[arc] = true + end + if not ( self.is_last[head] or self.is_last[tail]) then + if not self.digraph:arc(self.next_node[tail],self.next_node[head]) then + -- arc disappears while nodes are still in the next snapshot + self.is_last[arc] = true + end + else + -- arc and at least one node disappears in the next snapshot + self.is_last[arc] = true + end + self.move_on_enter[arc] = self.move_on_enter[head] or self.move_on_enter[tail] + self.move_on_leave[arc] = self.move_on_leave[head] or self.move_on_leave[tail] + end + + -- 2. precompute the unmodified edges + local supergraph = self.supergraph + + for _, superarc in ipairs(supergraph.arcs) do + local vis_objects = {} + self.visible_objects[superarc] = vis_objects + + local previous_arc + local previous_representant + + for _, s in ipairs(supergraph.arc_snapshots[superarc]) do + local head = supergraph:getSnapshotVertex(superarc.head, s) + local tail = supergraph:getSnapshotVertex(superarc.tail, s) + -- use the digraph because the snapshot arc is not synced + local arc = self.digraph:arc(tail, head) + + local modified = false + local opt_array = arc:optionsArray('modified') + for i = 1,#opt_array.aligned do + modified = modified or opt_array[i] + end + + if modified or + not eq_arc(arc, previous_arc) or self.is_first[arc] then + --modified + previous_representant = arc + vis_objects[s] = arc + if previous_arc then + self.last_rep[previous_arc] = true + end + else + -- unmodified + previous_representant = previous_representant or arc + vis_objects[s] = previous_representant + end + previous_arc = arc + end + end +end +-- Done + +return GraphAnimationCoordination diff --git a/graphics/pgf/base/tex/generic/graphdrawing/lua/pgf/gd/experimental/evolving/GreedyTemporalCycleRemoval.lua b/graphics/pgf/base/tex/generic/graphdrawing/lua/pgf/gd/experimental/evolving/GreedyTemporalCycleRemoval.lua new file mode 100644 index 0000000000..0c348bcc8f --- /dev/null +++ b/graphics/pgf/base/tex/generic/graphdrawing/lua/pgf/gd/experimental/evolving/GreedyTemporalCycleRemoval.lua @@ -0,0 +1,177 @@ +-- Copyright 2015 by Malte Skambath +-- +-- This file may be distributed an/or modified +-- +-- 1. under the LaTeX Project Public License and/or +-- 2. under the GNU Public License +-- +-- See the file doc/generic/pgf/licenses/LICENSE for more information + + +local GreedyTemporalCycleRemoval = {} + +-- Imports +local lib = require "pgf.gd.lib" +local declare = require("pgf.gd.interface.InterfaceToAlgorithms").declare + +local Vertex = require "pgf.gd.model.Vertex" +local Digraph = require "pgf.gd.model.Digraph" +local Coordinate = require "pgf.gd.model.Coordinate" + +local PriorityQueue = require "pgf.gd.lib.PriorityQueue" + +-- Keys + +--- + +declare { + key = "split critical arc head", + type = "boolean", + initial = true, + summary = "Specifies, that for a critical the tail node is separated" +} + +--- + +declare { + key = "split critical arc tail", + type = "boolean", + initial = true, + summary = "Specifies, that for a critical the tail node is separated" +} + +--- + +declare { + key = "greedy temporal cycle removal", + algorithm = GreedyTemporalCycleRemoval, + phase = "temporal cycle removal", + phase_default = true, + summary = [[" + A temporal dependency cycle is a cyclic path in the supergraph of + an evolving graph. Use this key if you want remove all temporal + dependency cycles by a greedy strategy which incrementally inserts + edge checks if this edge creates a cycle and splits at least one node + into two supernode at a given time. + "]], + documentation = [[" + See ToDo + "]] +} + +-- Help functions +local function reachable(graph, v, w) + local visited = {} + local queue = PriorityQueue.new() + queue:enqueue(v,1) + while not queue:isEmpty() do + local vertex = queue:dequeue() + if vertex==w then + return true + end + local outgoings = graph:outgoing(vertex) + for _, e in ipairs(outgoings) do + local head = e.head + if not visited[head] then + visited[head] = true + if head == w then + return true + else + queue:enqueue(head,1) + end + end + end + end + return false +end + +-- Implementation + +function GreedyTemporalCycleRemoval:run() + local supergraph = assert(self.supergraph, "no supergraph passed") + local digraph = assert(self.digraph, "no digraph passed to the phase") + local split_tail = digraph.options["split critical arc tail"] + local split_head = digraph.options["split critical arc head"] + assert(split_tail or split_head, "without splitting nodes dependency cycles cannot be removed.") + + self:iterativeCycleRemoval(supergraph, split_tail, split_head) +end + +-- +-- Resolves all dependencies by splitting supernodes into multiple supernodes. +-- To resolve a cycle each edge will be inserted into a dependency graph +-- successively. Each time such edge closes a cycle the head and tail will +-- be split at the related snapshot. +-- +-- @param supergraph +-- +function GreedyTemporalCycleRemoval:iterativeCycleRemoval(supergraph, split_tail, split_head) + -- Build up the global dependency graph + -- A supernode v directly depends on another supernode w if + -- there is a snapshot in which w is a child of w + local dependency_graph = Digraph.new(supergraph) + local stable_arcs = {} + for i, snapshot in ipairs(supergraph.snapshots) do + --local tree = snapshot.spanning_tree + for _,tree in ipairs(snapshot.spanning_trees) do + local new_arcs = {} + + for _, e in ipairs(tree.arcs) do + if e.head.kind ~= "dummy" and e.tail.kind~="dummy" then + table.insert(new_arcs, e) + + local sv = supergraph:getSupervertex(e.tail) + local sw = supergraph:getSupervertex(e.head) + local dep_arc = dependency_graph:arc(sv, sw) + + + if (not dep_arc) then + -- check if the edge v->w closes a cycle in the dependency graph + --pgf.debug{dependency_graph} + local cycle_arc = reachable(dependency_graph, sw, sv) + dep_arc = dependency_graph:connect(sv,sw) +-- texio.write("\ncheck ".. sv.name.."->" .. sw.name) + if cycle_arc then + if split_tail then + supergraph:splitSupervertex(sv, { [1]=snapshot }) + end + if split_head then + supergraph:splitSupervertex(sw, { [1]=snapshot }) + end + + -- rebuild dependency graph + dependency_graph = Digraph.new(supergraph) + + for _, arc in ipairs(stable_arcs) do + dependency_graph:connect(arc.tail, arc.head) + end + + for _, arc in ipairs(new_arcs) do + local sv = supergraph:getSupervertex(arc.tail) + local sw = supergraph:getSupervertex(arc.head) + dependency_graph:connect(sv, sw) + end + end -- end of resolve cycle_arc + end + end + end + -- Stable Arcs: + for _, arc in ipairs(new_arcs) do + + local sv = supergraph:getSupervertex(arc.tail) + local sw = supergraph:getSupervertex(arc.head) + local deparc = dependency_graph:arc(sv, sw) +-- if not deparc or not stable_arcs[deparc] then +-- stable_arcs[deparc] = true + table.insert(stable_arcs, deparc) +-- end + + end + end -- end for spanning_tree + end -- end for snapshot +end + + +-- Done + +return GreedyTemporalCycleRemoval diff --git a/graphics/pgf/base/tex/generic/graphdrawing/lua/pgf/gd/experimental/evolving/Skambath2016.lua b/graphics/pgf/base/tex/generic/graphdrawing/lua/pgf/gd/experimental/evolving/Skambath2016.lua new file mode 100644 index 0000000000..2beec1e216 --- /dev/null +++ b/graphics/pgf/base/tex/generic/graphdrawing/lua/pgf/gd/experimental/evolving/Skambath2016.lua @@ -0,0 +1,875 @@ +-- Copyright 2015 by Malte Skambath +-- +-- This file may be distributed an/or modified +-- +-- 1. under the LaTeX Project Public License and/or +-- 2. under the GNU Public License +-- +-- See the file doc/generic/pgf/licenses/LICENSE for more information +-- + +-- Imports +require "pgf.gd.trees.ChildSpec" + +local Digraph = require "pgf.gd.model.Digraph" +local Vertex = require "pgf.gd.model.Vertex" + +local declare = require("pgf.gd.interface.InterfaceToAlgorithms").declare +local layered = require "pgf.gd.layered" +local tlayered = require "pgf.gd.experimental.evolving.layered" +local SpanningTreeComputation = require "pgf.gd.trees.SpanningTreeComputation" +local lib = require "pgf.gd.lib" + +local Storage = require "pgf.gd.lib.Storage" +local PriorityQueue = require "pgf.gd.lib.PriorityQueue" + +local Supergraph = require "pgf.gd.experimental.evolving.Supergraph" + +local LayoutPipeline = require "pgf.gd.control.LayoutPipeline" +local Direct = require "pgf.gd.lib.Direct" + +-- +-- +local Skambath2016 = {} + + +--- +declare { + key = "animated tree layout", + algorithm = Skambath2016, + postconditions = { + upward_oriented = true + }, + documentation_in = "pgf.gd.evolving.doc" +} + +--- +declare { + key = "animated binary tree layout", + use = { + { key = "animated tree layout" }, + { key = "minimum number of children", value = 2 }, + { key = "significant sep", value = 0 }, + }, + + documentation_in = "pgf.gd.evolving.doc" +} + +--- +declare { + key = "extended animated binary tree layout", + use = { + { key = "animated tree layout" }, + { key = "minimum number of children", value=2 }, + { key = "missing nodes get space" }, + { key = "significant sep", value = 0 } + }, + documentation_in = "pgf.gd.evolving.doc" +} + + + + + +-- Help functions + +--- +-- Borders models the borderlines / the line of border vertices +-- of a tree or subtree structure which can change over time. +-- Each ordered and rooted tree or subtree has vertices per layer for which they +-- are on the outer places. On the left or respectively on the right. +-- The field |left| and |right| stores the both borderlines. +-- A \emph{borderline} is an array. This array contains for each layer of the tree +-- a table mapping a given snapshot to the related border-vertex of the layer +-- in the snapshot. +-- +-- @field left +-- @field right +-- +local Borders = {} +Borders.__index = Borders + + +-- Namespace + +--require("pgf.gd.experimental.evolving").Borders = Borders + + +--- Create a new tree border description. +-- +-- +function Borders.new() + return setmetatable( {left = {}, right = {}}, Borders ) +end + +function Borders:addBoth(layer, snapshot, vertex) + local lleft = self.left[layer] or {} + local lright = self.right[layer] or {} + assert(not lleft[snapshot] and not lright[snapshot], "borders already defined for given layer and snapshot") + lleft[snapshot] = vertex + lright[snapshot] = vertex + self.left[layer] = lleft + self.right[layer] = lright +end + +function Borders:appendBelow(level, borders) + assert(borders, "invalid argument for borders. Value must not be 'nil'.") + assert((#self.left == #self.right) and (#self.left == level), "cannot be appended") + local new_level = borders:getLevel() + for i = 1, new_level do + self.left[i + level] = borders.left[i] + self.right[i + level] = borders.right[i] + end +end + +--- +-- +-- @return the number of levels in which bordervertices exists. +function Borders:getLevel() + assert(#self.left == #self.right, "different levels") + return #self.left +end + +function Borders.copyBordersAtSnapshot(source, target, snapshot) + local source_level = source:getLevel() + for i = 1, source_level do + level_border_left = target.left[i] or {} + level_border_right = target.right[i] or {} + assert(not level_border_left[snapshot] and not level_border_right[snapshot], + "border for a given snapshot already defined") + level_border_left[snapshot] = source.left[i][snapshot] + level_border_right[snapshot] = source.right[i][snapshot] + target.left[i] = level_border_left + target.right[i] = level_border_right + end +end + +-- +-- Adapt existing borders with the borders of a group which will be +-- placed on the right next to the existing borders. For each level +-- and time in which the group has a border the borders on the right +-- will be replaced with this border. If it is not existing in the old +-- borders then also the left border will be adapted +-- +-- @param borders The existing borders. These will be modified with +-- the borders of +-- +-- @param group_borders The borders of the group +-- +-- @param snapshots All snapshots in which checking for distances is necessary. +-- +local function adapt_borders(borders, group_borders, snapshots, ignore_dummies) + for level = 1, group_borders:getLevel() do + local l = borders.left[level] or {} + local r = borders.right[level] or {} + for _, s in pairs(snapshots) do + if ignore_dummies then + local gls,grs = group_borders.left[level][s], group_borders.right[level][s] + if gls~=nil then + if gls.kind~="dummy" then + l[s] = l[s] or gls + end + end + if grs~=nil then + if grs.kind~="dummy" then + r[s] = grs or r[s] + end + end + else + l[s] = l[s] or group_borders.left[level][s] + r[s] = group_borders.right[level][s] or r[s] + end + + end + borders.left[level] = l + borders.right[level] = r + end + +end + +-- +-- Shift all vertices of a group and their descendants +-- for a certain length into horizontal direction. +-- +-- @param shift the length all involved vertices +-- should be shifted in horizontal direction +-- +-- +-- @param group A group of the spanning trees that should be shifted. +-- A group entry has to map snapshots to root nodes +-- +-- @param snapshots An array of at least all snapshots in which the group +-- has a vertex +-- +-- @param descendants A table or Storage storing the list of descendants +-- for each vertex +-- +-- @return the highest x-coordinate of vertices in the group after the shift. +-- If there is no vertex which was shifted then -math.huge +-- will be returned +-- +local function shift_group(shift, group, snapshots, descendants) + assert(group,"no group passed") + assert(shift~=math.huge and shift ~=-math.huge, + "shift must be a valid finite length") + local shifted = {} -- remember which vertex was shifted during this run + local anchor = -math.huge + for _, s in ipairs(snapshots) do + local v = group[s] + if not shifted[v] and v then + v.pos.x = v.pos.x + shift + shifted[v] = true + + -- also shift all descendants of the group vertex + for _, d in ipairs(descendants[v]) do + if not shifted[d] then + d.pos.x = d.pos.x + shift + shifted[d] = true + end + end + anchor = math.max(anchor, v.pos.x ) + end + end + return anchor +end + +-- +-- Traverse through the spanning tree |tree| of a snapshot and sort +-- the child nodes into groups. A group summarizes for a given parent +-- node all children node over time that are at the same child +-- position. The k-th child group groups[i] maps each snapshot to the +-- k-th node in the related snapshot. +-- +-- +-- @field supergraph the supergraph +-- +-- @field tree the tree or spanning tree to decide the indices of the +-- child nodes +-- +-- @field childgroups a Storage which contains the list of childgroups +-- for each supernode +-- +-- @field snapshot +-- +local function precompute_childgroups(supergraph, tree, node, childgroups, snapshot) + local outgoings = tree:outgoing(node) + if #outgoings > 0 then + local supervertex = supergraph:getSupervertex(node) + local groups = childgroups[supervertex] or {} + for i, e in ipairs(outgoings) do + + group = groups[i] or {} + assert(e.head, "no edge") + group[snapshot] = e.head + groups[i] = group + precompute_childgroups(supergraph, tree, e.head, childgroups, snapshot) + end + assert(supervertex, "no mv") + childgroups[supervertex] = groups + end +end + +-- +-- Use this function to compute the horizontal positions of all +-- vertices in a tree by accumulation of the relative shifts on the +-- path from the root to the vertex recursively. +-- +-- @param tree the tree in which the vertex's position should be +-- computed. +-- +-- @param vertex the next vertex that gets its absolute coordinate. +-- +-- @param shifts a Storage, which stores for each node the relative +-- shift between the vertex and its parent. +-- +-- @param abs_shift the sum of all relative shifts on the path from +-- the root to the vertex. +-- +local function accumulate_hpos(tree, vertex, shifts, abs_shift) + local new_shift = abs_shift + shifts[vertex] + local test = vertex.pos.x + vertex.pos.x = new_shift +-- if vertex.pos.x - test > 0.0001 then texio.write("X")end + local outgoings = tree:outgoing(vertex) + for _, e in ipairs(outgoings) do + accumulate_hpos(tree, e.head, shifts, new_shift) + end +end + + +local function get_next(border_pair, next) + local nl = next.left[border_pair.left] + local nr = next.right[border_pair.right] + assert ((nl and nr) or (not nl and not nr)) + return {left = nl, right = nr, + } +end + +local function add_shift(abs_shift, border_pair, next) + abs_shift.left = abs_shift.left + next.left_shift[border_pair.left] + abs_shift.right = abs_shift.right + next.right_shift[border_pair.right] +end + +-- +-- Given a tree, computes the required distance between the i-th and the (i+1)-th subtree +-- of the vertex |snapshot_vertex|. +-- +-- @param shifts a Storage, which contains for each vertex the relative horizontal shift +-- to its parent vertex. +-- +function Skambath2016:computeRequiredDistance(tree, vertex, i, shifts, next) + local outgoings = tree:outgoing(vertex) +-- texio.write("\n::"..vertex.name.. " "..i.."|"..(i+1)) + if #outgoings > 0 then + local clumb = {left = outgoings[1].head,right = outgoings[i].head} + if clumb.right.kind=="dummy" then shifts[clumb.right] = 0 end + local v0 = outgoings[i].head + local v1 = outgoings[i+1].head + local shift = layered.ideal_sibling_distance(self.adjusted_bb, self.ugraph, v0, v1) + shifts[clumb.right] + local last0 = {left = clumb.left, right = clumb.right} + local last1 = {left = v1, right = v1} + local next0 = get_next(last0, next) + local next1 = get_next(last1, next) + local abs_shift0 = {left = shifts[clumb.left], right = shifts[clumb.right]} + local abs_shift1 = {left = 0, right = 0} + + while (next0.left and next1.left) do + add_shift(abs_shift0, last0, next) + add_shift(abs_shift1, last1, next) + + shift = math.max(shift, + layered.ideal_sibling_distance(self.adjusted_bb, + self.ugraph, + next0.right, + next1.left) + + abs_shift0.right - abs_shift1.left) +-- texio.write("\n | "..(next0.right.name or "dummy").."<->"..(next1.left.name or "dummy").." :\t"..shift) + last0, last1 = next0, next1 + next0 = get_next(next0, next) + next1 = get_next(next1, next) + end + return shift, {l0 = last0, l1 = last1, n0 = next0, n1 = next1,abs_shift1 = abs_shift1,abs_shift0=abs_shift0} + -- end + else + return 0 + end +end + +local function apply_shift(tree, vertex, i, shifts, next, border_ptr, shift) + local outgoings = tree:outgoing(vertex) +-- texio.write("\n" .. (vertex.name or "dummy")..": ".. shift ) + if #outgoings >= (i+1) then + assert(border_ptr, "unexpected error") + local last0 = border_ptr.l0 + local last1 = border_ptr.l1 + local next0 = border_ptr.n0 + local next1 = border_ptr.n1 + local abs0 = border_ptr.abs_shift0 + local abs1 = border_ptr.abs_shift1 + local vbase = outgoings[1].head -- before centering the 1st vertex is at x=0 + local v0 = outgoings[i].head + local v1 = outgoings[i+1].head + if v0.kind=="dummy" then shifts[v0] = 0 end + shifts[v1] = shifts[vbase] + shift + if next0.left then + assert(next0.right and next0.left, "failA") + -- pointer from T_i to T_{i+0} + next.right[last1.right] = next0.right + next.right_shift[last1.right] = - shift - abs1.right + (abs0.right + next.right_shift[last0.right]) + elseif next1.right then + assert(next1.right and next1.left, "") + -- pointer from T_{i+0} to T_i + -- texio.write(last0.left .." -> " ..next1.left) + next.left[last0.left] = next1.left +-- pgf.debug{last0,abs0,abs1,last1} + next.left_shift[last0.left] = shift - abs0.left + (abs1.left + next.left_shift[last1.left] ) + + else + -- both trees have the same height + end + end +end + +-- Implementation + +function Skambath2016:run() + local layers = Storage.new() + local descendants = Storage.new() + local childgroups = Storage.new() + + local phases = self.digraph.options.algorithm_phases + + local so_class = phases['supergraph optimization'] + local ga_class = phases['evolving graph animation'] + local cr_class = phases['temporal cycle removal'] + + self.extended_version = self.digraph.options['missing nodes get space'] + self.supergraph = Supergraph.generateSupergraph(self.digraph) + local supergraph_original = Supergraph.generateSupergraph(self.digraph) + + -- optimize graph by splitting nodes by trivial criteria + so_class.new { + main_algorithm = self, + supergraph = self.supergraph, + digraph = self.digraph, + ugraph = self.ugraph + }:run() + + + self:precomputeSpanningTrees() + + -- Resolve cyclic dependencies if exists. + cr_class.new { + main_algorithm = self, + supergraph = self.supergraph, + digraph = self.digraph, + }:run() + + + + self:precomputeDescendants(layers, descendants) + self:precomputeChildgroups(childgroups) + + self:computeHorizontalLayout(childgroups, descendants) +-- self:computeHorizontalLayoutFast() + + -- vertical positions + tlayered.arrange_layers_by_baselines(layers, + self.adjusted_bb, + self.ugraph, + self.supergraph.snapshots, + self.supergraph.vertex_snapshots) + + -- animate graph + ga_class.new { + main_algorithm = self, + supergraph = supergraph_original, + digraph = self.digraph, + ugraph = self.ugraph + }:run() +end + +-- +-- Compute the required shift value for a second tree to guarantee +-- a required node distance. +-- @field right_borders The Border data structure for the right border of +-- the left tree +-- @field left_borders The Border data structure for the left border of +-- the right tree +-- @field selected_snapshots if you set this value with an array of snapshots +-- only the predefined snapshots are used in the border +-- computation. +-- +function Skambath2016:computeMinShift(right_borders, left_borders, selected_snapshots) + local shift = -math.huge + local max_level = math.min(#right_borders, #left_borders) + local first_shift = 0 + local snapshots = selected_snapshots or self.supergraph.snapshots + + for layer = 1, max_level do + local rb, lb = right_borders[layer], left_borders[layer] + for _,s in ipairs(snapshots) do + + local v1,v2 = rb[s],lb[s] + if v1 and v2 then + local local_shift = layered.ideal_sibling_distance(self.adjusted_bb, self.ugraph, v1, v2) + v1.pos.x - v2.pos.x + shift = math.max(shift, local_shift) + end + end + if layer == 1 then + first_shift = shift + end + end + + local is_significant = false + + if max_level > 1 and shift<=first_shift then + -- if the necessary shift of the subtrees + -- is the minimum required shift between two + -- nodes than a node is significant + is_significant = true + end + + if shift <= -math.huge then + shift = 0 + end + + if is_significant then + shift = shift + self.ugraph.options['significant sep'] + end + + return shift +end + + +-- +-- The main algorithm: This method computes the layout for each vertex. +-- For this all supervertices are visited in a topological order to their dependency. +-- If a . This requires the supergraph to be acyclic. If this is not the case +-- the calling process has to remove all cycles otherwise the x-coordinate will +-- not be computed for every vertex. +-- +function Skambath2016:computeHorizontalLayout(groups, descendants) + local subtree_borders = Storage.new() + local dep_counter = {} + local visited = {} + local queue = PriorityQueue.new() + local dependency_graph = Digraph.new() + for _, vertex in ipairs(self.supergraph.vertices) do + dep_counter[vertex] = 0 + dependency_graph:add {vertex} + end + + -- 1. Initialize Dependencies + + for _, snapshot in ipairs(self.supergraph.snapshots) do + for _, spanning_tree in ipairs(snapshot.spanning_trees) do + for _, arc in ipairs(spanning_tree.arcs) do + + local head = self.supergraph:getSupervertex(arc.head) + local tail = self.supergraph:getSupervertex(arc.tail) + + if(head and tail) then + if not dependency_graph:arc(tail, head) then + dependency_graph:connect(tail, head) + dep_counter[tail] = dep_counter[tail] + 1 + end + end + end + end + end + + -- 2. Find independent vertices + for _, vertex in ipairs(dependency_graph.vertices) do + local outgoings = dependency_graph:outgoing(vertex) + if #outgoings == 0 then + queue:enqueue(vertex, 1) + end + end + + -- 2. + while not queue:isEmpty() do + local vertex = queue:dequeue() + local vertex_snapshots = self.supergraph:getSnapshots(vertex) + + -- a. Resolve dependencies on this vertex: + local incomings = dependency_graph:incoming(vertex) + for _, e in ipairs(incomings) do + dep_counter[e.tail] = dep_counter[e.tail] - 1 + if dep_counter[e.tail] == 0 then + queue:enqueue(e.tail, 1) + end + end + + -- b. Compute borders of this supervertex: + local vertex_borders = Borders.new() + for _, s in ipairs(vertex_snapshots) do + local snapshot_vertex = self.supergraph:getSnapshotVertex(vertex, s) + vertex_borders:addBoth(1, s, snapshot_vertex) + snapshot_vertex.pos.x = 0 + snapshot_vertex.pos.y = 0 + end + + local vertex_groups = groups[vertex] + local last_pos_x = 0 + if vertex_groups then + -- c. Compute borders of groups: + local all_group_borders = {} + for i, group in ipairs(vertex_groups) do + local group_boders = Borders.new() + for _,s in ipairs(vertex_snapshots) do + local child = group[s] + if child then + local child_borders + if not (child.kind == "dummy") then + local superchild = self.supergraph:getSupervertex(child) + child_borders = subtree_borders[superchild] or Borders.new() + else + child_borders = Borders.new() + child_borders:addBoth(1, s, child) + end + assert(child.pos~=math.huge, "invalid child pos") + shift_group(-child.pos.x,{[s]=child},{[1]=s}, descendants) + Borders.copyBordersAtSnapshot(child_borders, group_boders, s) + end + end + all_group_borders[i] = group_boders + end + + -- d. Place groups and merge borders of groups: + local last_group = nil + last_pos_x = 0 + local merged_borders = Borders.new() + local final_borders = Borders.new() + for i, group in ipairs(vertex_groups) do + local group_borders = all_group_borders[i] + if last_group_borders then + -- i. compute minimal shift + + local shift + shift = self:computeMinShift(merged_borders.right, group_borders.left) + + + assert(shift ~= math.huge and shift~=-math.huge, "invalid shift") + + -- ii. shift group + local anchor = shift_group(shift, group,vertex_snapshots, descendants) + last_pos_x = anchor + end + last_group_borders = group_borders + + -- iii. adapt borders + adapt_borders(merged_borders, + group_borders, + self.supergraph.snapshots) + adapt_borders(final_borders, + group_borders, + self.supergraph.snapshots, + not self.extended_version) + end -- for (group) + vertex_borders:appendBelow(1, final_borders) + end + -- e. store borders: + assert(last_pos_x~=math.huge and last_pos_x~=-math.huge, "invalid position") + local x = ((last_pos_x) - 0)/2 + 0 + assert(x~=math.huge and x~=-math.huge, "invalid position") + for _,s in ipairs(vertex_snapshots) do + local snapshot_vertex = self.supergraph:getSnapshotVertex(vertex, s) + snapshot_vertex.pos.x = x + end + + subtree_borders[vertex] = vertex_borders + end + + -- align roots + for _, s in ipairs(self.supergraph.snapshots) do + local lastroot + local rborder + for i, spanning_tree in ipairs(s.spanning_trees) do + local root = spanning_tree.root + local rootborders = subtree_borders[self.supergraph:getSupervertex(root)] + shift_group(-root.pos.x,{[s]=root},{[1]=s}, descendants) + if i>1 then + local l = subtree_borders[self.supergraph:getSupervertex(lastroot)] + local r = rootborders + shift = math.max(self:computeMinShift(l.right, r.left, {[1]=s}), + self:computeMinShift(rborder.right,r.left, {[1]=s})) + shift_group(shift,{[s]=root},{[1]=s}, descendants) + else + rborder = Borders.new() + end + adapt_borders(rborder,rootborders,self.supergraph.snapshots,false) + lastroot = root + end + end +end + +-- +-- The main algorithm: This method computes the layout for each vertex. +-- For this all supervertices are visited in a topological order to their dependency. +-- If a . This requires the supergraph to be acyclic. If this is not the case +-- the calling process has to remove all cycles otherwise the x-coordinate will +-- not be computed for every vertex. +-- +function Skambath2016:computeHorizontalLayoutFast() + local all_trees = Storage.new() + local dep_counter = {} + local visited = {} + local queue = PriorityQueue.new() + local dependency_graph = Digraph.new() + local shifts = Storage.new() + local next = Storage.new() + for _, vertex in ipairs(self.supergraph.vertices) do + dep_counter[vertex] = 0 + dependency_graph:add {vertex} + end + + + -- I. Initialize Dependencies (Build Dependency Graph) + for _, snapshot in ipairs(self.supergraph.snapshots) do + for _, spanning_tree in ipairs(snapshot.spanning_trees) do + table.insert(all_trees, spanning_tree) + shifts[spanning_tree] = Storage.new() + next[spanning_tree] = {left= Storage.new(), + right= Storage.new(), + left_shift = Storage.new(), + right_shift = Storage.new() + } + + for _, arc in ipairs(spanning_tree.arcs) do + local head = self.supergraph:getSupervertex(arc.head) + local tail = self.supergraph:getSupervertex(arc.tail) + + if(head and tail) then + if not dependency_graph:arc(tail, head) then + dependency_graph:connect(tail, head) + dep_counter[tail] = dep_counter[tail] + 1 + end + end + end + end + end + + -- II. Visit vertices in topological ordering + -- Find independent vertices + for _, vertex in ipairs(dependency_graph.vertices) do + local outgoings = dependency_graph:outgoing(vertex) + if #outgoings == 0 then + queue:enqueue(vertex, 1) + end + end + + while not queue:isEmpty() do + -- Next node in topological order + local vertex = queue:dequeue() +-- texio.write("\n\n --- "..vertex.name .. " ---") + --pgf.debug{next} + local vertex_snapshots = self.supergraph:getSnapshots(vertex) + + -- a. Resolve dependencies on this vertex: + local incomings = dependency_graph:incoming(vertex) + for _, e in ipairs(incomings) do + dep_counter[e.tail] = dep_counter[e.tail] - 1 + if dep_counter[e.tail] == 0 then + queue:enqueue(e.tail, 1) + end + end + + -- b. Compute maximum number of children over time: + local num_children = 0 + for _, s in ipairs(vertex_snapshots) do + local v = self.supergraph:getSnapshotVertex(vertex, s) + local tree = s.spanning_trees[1] + num_children = math.max(num_children, #(tree:outgoing(v))) + shifts[tree][v] = 0 + end + + -- c. Shift all subtrees in all snapshots: + local hlp_ptr = Storage.new() + local max_shift = 0 + for i = 1, (num_children - 1) do + -- i) Compute the necessary shift between the i-th and (i+1)-th subtrees (per snapshot): + local min_shift = 0 + for t, s in ipairs(vertex_snapshots) do + local snapshot_vertex = self.supergraph:getSnapshotVertex(vertex, s) + local tree = s.spanning_trees[1] + local req_shift, hptr + req_shift, hptr = self:computeRequiredDistance(tree, + snapshot_vertex, + i, + shifts[tree], + next[tree] + ) + hlp_ptr[t] = hptr +-- texio.write(" -> \t"..req_shift) + min_shift = math.max(min_shift, req_shift) + end + +-- texio.write("\n \t\t".. min_shift ) + + -- ii) Synchronize distance between neighbored subtrees and apply shifts + for t, s in ipairs(vertex_snapshots) do + local snapshot_vertex = self.supergraph:getSnapshotVertex(vertex, s) + local tree = s.spanning_trees[1] + apply_shift(tree, snapshot_vertex, i, shifts[tree], next[tree], hlp_ptr[t], min_shift) + end + + max_shift = min_shift + end + + for t, s in ipairs(vertex_snapshots) do + local snapshot_vertex = self.supergraph:getSnapshotVertex(vertex, s) + local tree = s.spanning_trees[1] + local outgoings = tree:outgoing(snapshot_vertex) + +-- next[tree].left[snapshot_vertex] = outgoings[1].head + + + for i = 1,#outgoings do + if i==1 then + next[tree].left_shift[snapshot_vertex] = - max_shift / 2 + next[tree].left[snapshot_vertex]= outgoings[i].head + end + shifts[tree][outgoings[i].head] = shifts[tree][outgoings[i].head] - max_shift / 2 + next[tree].right[snapshot_vertex] = outgoings[i].head + next[tree].right_shift[snapshot_vertex] = shifts[tree][outgoings[i].head] + end + + end + + end -- end while (all vertices have been processed) + + -- III. Accumulate absolute horizontal coordinates + for _, tree in ipairs(all_trees) do + accumulate_hpos(tree, tree.root, shifts[tree], 0) + end +end + + + + +function Skambath2016:precomputeTreeDescendants(tree, node, depth, layers, descendants) + local my_descendants = { node } + + for _,arc in ipairs(tree:outgoing(node)) do + local head = arc.head + self:precomputeTreeDescendants(tree, head, depth+1, layers, descendants) + for _,d in ipairs(descendants[head]) do + my_descendants[#my_descendants + 1] = d + end + end + layers[node] = depth + descendants[node] = my_descendants +end + +function Skambath2016:precomputeDescendants(layers, descendants) + for _,snapshot in ipairs(self.supergraph.snapshots) do + for _, spanning_tree in ipairs(snapshot.spanning_trees) do + self:precomputeTreeDescendants(spanning_tree, spanning_tree.root, 1, layers, descendants) + end + end +end + + +-- +-- +-- +function Skambath2016:precomputeChildgroups(childgroups) + for _,s in ipairs(self.supergraph.snapshots) do + for _,spanning_tree in ipairs(s.spanning_trees) do + precompute_childgroups(self.supergraph, spanning_tree, spanning_tree.root, childgroups, s) + end + end +end + +-- +-- Compute a for each connected component of each +-- snapshot and appends the result for a snapshot s to +-- the array s.spanning_trees. +-- +function Skambath2016:precomputeSpanningTrees() + local events = assert(self.scope.events, + "no events found for the spanning tree computation") + + for i, s in ipairs(self.supergraph.snapshots) do + -- The involved snapshot graph: + local s_copy = Digraph.new(s) + for _,a in ipairs(s.arcs) do + local new_a = s_copy:connect(a.tail,a.head) + new_a.syntactic_edges = a.syntactic_edges + end + s.spanning_trees = s.spanning_trees or {} + -- Step 1: Decompose the snapshot into its connected components + local syntactic_components = LayoutPipeline.decompose(s_copy) + for i, syntactic_component in ipairs (syntactic_components) do + local tree = SpanningTreeComputation.computeSpanningTree(syntactic_component, true, events) + s.spanning_trees[i] = tree + end + end +end + +return Skambath2016 + + + + diff --git a/graphics/pgf/base/tex/generic/graphdrawing/lua/pgf/gd/experimental/evolving/Supergraph.lua b/graphics/pgf/base/tex/generic/graphdrawing/lua/pgf/gd/experimental/evolving/Supergraph.lua new file mode 100644 index 0000000000..99e79919b5 --- /dev/null +++ b/graphics/pgf/base/tex/generic/graphdrawing/lua/pgf/gd/experimental/evolving/Supergraph.lua @@ -0,0 +1,571 @@ +-- Copyright 2015 by Malte Skambath +-- +-- This file may be distributed an/or modified +-- +-- 1. under the LaTeX Project Public License and/or +-- 2. under the GNU Public License +-- + +-- Imports +local lib = require "pgf.gd.lib" + +local Vertex = require "pgf.gd.model.Vertex" +local Digraph = require "pgf.gd.model.Digraph" +local Storage = require "pgf.gd.lib.Storage" + + +local declare = require("pgf.gd.interface.InterfaceToAlgorithms").declare + +--- +-- Each |Supergraph| instance is a |Digraph| instance which represents +-- the graph by union operation on all graphs G_i of an evolving graph +-- $G=(G_1, G_2, \dots, G_n)$. Additional to that all references to +-- the snapshot-graphs are shared such that is possible to get access +-- to all vertices for each snapshot graph in a sequence. A vertex of +-- an evolving graph may exists at different times, thus in in +-- different snapshots. Each vertex will be a vertex in the supergraph +-- and if there is a single snapshot in which two vertices are +-- connected by an edge they are connected in the supergraph. +-- +-- Note that in \tikzname\ a \emph{node} is more than a single dot. A node +-- has a content and different properties like background-color or a +-- shape. Formally this can be modeled by function mapping vertices +-- to their properties. For evolving graphs this could be done in the +-- same way. As this is difficult to be realized in PGF because there +-- is no basic support for time dependent properties on nodes, each +-- vertex will be displayed over time by different single +-- (snapshot-)nodes which can have different visual properties. This +-- means for a vertex which we call |supervertex| in the following we +-- will have a (snapshot-)node for each time stamp. +-- +-- \medskip +-- \noindent\emph{Snapshots.} +-- Since an evolving graph is a sequence of different snapshot-graphs +-- $G_i$ each snapshot is assigned to a time +-- +-- +-- @field vertex_snapshots This storage maps each pgf-node to the snapshots +-- in which they are visible. +-- +-- @field supervertices This storage maps each pgf-node to its supervertex +-- which represents all pgf-vertices assigned to the same node +-- +-- @field supervertices_by_id This storage maps a node identifier to the +-- related supervertex such that PGF-nodes which belonging to +-- the same superverticex can be identified +-- +-- @field snapshots An array of all snapshots. Sorted in ascending order +-- over the timestamps of the snapshots. +-- +-- @field arc_snapshots A table storing all snapshots of a supervertex in which +-- the related nodes are connected. Using a snapshot as key you can check +-- if a given snapshot is in the array. +-- +-- Assume we want to iterate over all snapshots +-- for a certain pair of supernodes in which they are connected +-- by an arc. The arc_snapshots storage helps in this case: +-- % +-- \begin{codeexample}[code only, tikz syntax=false] +-- local supergraph = Supergraph.generateSupergraph(self.digraph) +-- local u = supergraph.vertices[1] +-- local v = supergraph.vertices[2] +-- +-- local snapshots = supergraph.arc_snapshots[supergraph:arc(u, v)] +-- for _, snapshot in ipairs(snapshots) do +-- do_something(snapshot) +-- end +-- \end{codeexample} +-- +local Supergraph = lib.class { base_class = Digraph } + +-- Namespace +--require("pgf.gd.experimental.evolving").Supergraph = Supergraph + +Supergraph.__index = + function (t, k) + if k == "arcs" then + return Digraph.__index(t,k) + else + return rawget(Supergraph, k) or rawget(Digraph, k) + end + end + +function Supergraph.new(initial) + local supergraph = Digraph.new(initial) + setmetatable(supergraph, Supergraph) + + supergraph.vertex_snapshots = Storage.new() + supergraph.supervertices = Storage.new() + supergraph.supervertices_by_id = {} + supergraph.arc_snapshots = Storage.newTableStorage() + + return supergraph + +end + + +local get_snapshot + +--- +-- Generate or extract a snapshot instance for a given snapshot time. +-- +-- @param snapshots An array of all existing snapshots +-- @param timestamps A table which maps known timestamps to their +-- related snapshots +-- @param ugraph The ugraph of the underlying graph structure +-- @param snapshot_time +-- +-- @return The snapshot instance found in the snapshots array for the +-- wanted timestamp snapshot_time if it doesn't exists a new snapshot +-- will be generated and added to the arrays +-- +function get_snapshot(snapshots, timestamps, ugraph, snapshot_time) + local snapshot + local snapshot_idx = timestamps[snapshot_time] + + if not snapshot_idx then + -- store snapshot if it doesn't exists + snapshot_idx = timestamps.n + 1 + timestamps[snapshot_time] = snapshot_idx + timestamps.n = timestamps.n + 1 + snapshot = Digraph.new { + syntactic_digraph = ugraph.syntactic_digraph, + options = ugraph.options + } + snapshot.timestamp = snapshot_time + snapshots[snapshot_idx] = snapshot + else + snapshot = snapshots[snapshot_idx] + end + assert(snapshot~=nil, "an unexpected error occurred") + return snapshot +end + + +--- +-- Generate a new supergraph to describe the whole evolving graph by +-- collecting all temporal information from the digraph and the node +-- options. All nodes in the |digraph| require a |snapshot| and +-- a |supernode| option. To identify a (snapshot-)node with its +-- supernode and snapshot. +-- +-- @param digraph +-- +-- @return The supergraph which is a |Digraph| that has a supervertex +-- for each set of snapshot-vertices with the same |supernode| +-- attribute. +-- +function Supergraph.generateSupergraph(digraph) + local new_supergraph + new_supergraph = Supergraph.new { + syntactic_digraph = digraph.syntactic_digraph, + options = digraph.options, + digraph = digraph, + } + + -- array to store the supervertices for a given vertex name + local local_snapshots = {} -- array to store each snapshot graphs + + local timestamps = { n = 0 } -- set of snapshot times + + -- separate and assign vertices to their snapshots and supervertices + for i,vertex in ipairs(digraph.vertices) do + local snapshot_time = assert(vertex.options["snapshot"], "Missing option 'snapshot' for vertex ".. vertex.name ..". ") + local supernode_name = assert(vertex.options["supernode"], "Missing option 'supernode' for vertex"..vertex.name..". ") + + local snapshot = get_snapshot(local_snapshots, timestamps, digraph, snapshot_time) + local supervertex = new_supergraph.supervertices_by_id[supernode_name] + + if not supervertex then + -- first appearance of the supernode id + supervertex = Vertex.new { + kind = "super", + name = supernode_name + } + supervertex.snapshots = {} + supervertex.subvertex = {} + new_supergraph.supervertices_by_id[supernode_name] = supervertex + new_supergraph:add{supervertex} + + supervertex.options = {} + supervertex.options = vertex.options + end + + snapshot:add{vertex} + + new_supergraph.supervertices[vertex] = supervertex + new_supergraph.vertex_snapshots[vertex] = snapshot + new_supergraph:addSnapshotVertex(supervertex, snapshot, vertex) + end + + -- Create edges + for i, e in ipairs(digraph.arcs) do + local u,v = e.tail, e.head + local snapshot_tail = new_supergraph.vertex_snapshots[e.tail] + local snapshot_head = new_supergraph.vertex_snapshots[e.head] + + assert(snapshot_head == snapshot_tail, "Arcs must connect nodes that exist at the same time.") + + -- connect in the snapshot graph + local arc = snapshot_tail:connect(u,v) + + -- connect in the supergraph: + local super_tail = new_supergraph.supervertices[u] + local super_head = new_supergraph.supervertices[v] + + new_supergraph:assignToSuperarc(super_tail, super_head, snapshot_tail) + end + + -- snapshots in temporal order + table.sort(local_snapshots, + function(s1,s2) + return s1.timestamp < s2.timestamp + end ) + + local previous_snapshot + + for i,s in ipairs(local_snapshots) do + local start = -math.huge + if previous_snapshot then + start = (s.timestamp - previous_snapshot.timestamp) / 2 + previous_snapshot.timestamp + previous_snapshot.interval.to = start + end + s.interval = { from = start , to = math.huge } + previous_snapshot = s + end + + new_supergraph.snapshots = local_snapshots + new_supergraph.snapshots_indices = Storage.new() + + for i, s in ipairs(new_supergraph.snapshots) do + new_supergraph.snapshots_indices[s] = i + end + + return new_supergraph +end + + +function Supergraph:getSnapshotStaticDuration(snapshot) + assert(snapshot, "a snapshot as parameter expected, but got nil") + local idur = snapshot.interval.to - snapshot.interval.from + assert(idur, "unexpected nil-value") + local d1 = snapshot.interval.to - snapshot.timestamp + local d2 = snapshot.timestamp - snapshot.interval.from + local dm = math.min(d1,d2) + if (idur >= math.huge and dm < math.huge) then + return dm -- [-\infty,t] or [t,\infty] + elseif idur >= math.huge then + return 0 -- only one snapshot [-\infty,\infty] + else + return d1 + d2 -- [t_1, t_2] + end +end + +--- +-- Get the durations of the graph in which snapshots are given which is exactly +-- the time between the first and the last defined snapshot +-- +-- @return The time between the last and first snapshot in seconds +function Supergraph:getDuration() + local first_snapshot = self.snapshots[1] + local last_snapshot = self.snapshots[#self.snapshots] + return last_snapshot.timestamp - first_snapshot.timestamp +end + +--- +-- +-- @return The ratio of the time of a snapshot related to the global duration of the whole +-- evolving trees. (The time between the last and first snapshot) +function Supergraph:getSnapshotRelativeDuration(snapshot) + if self:getDuration() == 0 then + return 1 + else + return self:getSnapshotStaticDuration(snapshot) / self:getDuration() + end +end + +--- +-- Give the supervertex for a certain pgf-vertex (vertex of a snapshot) +-- +-- @param vertex A vertex of a snapshot. +-- +-- @return A supervertex in the supergraph for the given vertex, nil if no +-- supervertex was assigned before. +-- +function Supergraph:getSupervertex(vertex) + assert(vertex, "vertex required") + assert(self.supervertices, "supervertex table is not defined") + return self.supervertices[vertex] +end + +function Supergraph:getSuperarc(arc) + local superhead = self:getSupervertex(arc.head) + local supertail = self:getSupervertex(arc.tail) + local arc = assert(self:arc(supertail, superhead),"unexpected problem") + return arc +end + +function Supergraph:getSnapshots(supervertex) + return supervertex.snapshots +end + +--- +-- Find the snapshot-instance for a given pgf-vertex +-- (which is a vertex for one certain snapshot) +-- +-- @param vertex A vertex for which you want to get the related snapshot +-- +-- @return The snapshot which contains the given vertex as vertex. +function Supergraph:getSnapshot(vertex) + return self.vertex_snapshots[vertex] +end + +--- +-- For a given supervertex get the related vertex for a snapshot +-- +-- @param supervertex +-- +-- @param snapshot +-- +-- @return The vertex of the supervertex at the specified snapshot +-- +function Supergraph:getSnapshotVertex(supervertex, snapshot) + assert(supervertex, "supervertex must not be nil") + assert(snapshot, "snapshot must not be nil") + return supervertex.subvertex[snapshot] +end + + +function Supergraph:consecutiveSnapshots(snapshot1, snapshot2, n) + assert(snapshot1 and snapshot2, "no snapshot passed") + local idx1 = self.snapshots_indices[snapshot1] --or -1 + local idx2 = self.snapshots_indices[snapshot2] --or -1 + local d = n or 1 + + return (idx2-idx1 <= d) or (idx1-idx2 <= d) +end + +function Supergraph:consecutive(vertex1, vertex2, n) + local s1 = self:getSnapshot(vertex1) + local s2 = self:getSnapshot(vertex2) + return self:consecutiveSnapshots(s1, s2, n) +end + +--- +-- Write pack all position information to the nodes of each snapshot +-- such that all nodes with the same supervertex have the same position +-- +-- @param ugraph An undirected graph for which the vertices should get +-- their positions from the supergraph. +-- +function Supergraph:sharePositions(ugraph, ignore) + + for _,vertex in ipairs(ugraph.vertices) do + if not ignore then + vertex.pos.x = self.supervertices[vertex].pos.x + vertex.pos.y = self.supervertices[vertex].pos.y + else + if not ignore.x then + vertex.pos.x = self.supervertices[vertex].pos.x + end + if not ignore.y then + vertex.pos.y = self.supervertices[vertex].pos.y + end + end + + + end +end + +function Supergraph:onAllSnapshotvertices(f, ugraph) + for _,vertex in ipairs(ugraph.vertices) do + local snapshot_vertex = self.supertvertices[vertex] + if snapshot_vertex then + f(vertex, snapshot_vertex) + end + end +end + +--- +-- Split a supervertex into new supervertices such that +-- for a given snapshot there is a new pseudo-supervertex. +-- This pseudo-supervertex will be assigned to all snapshots +-- after the given snapshot. +-- All snapshots of a new pseudo-supervertex are removed from +-- the original vertex. +-- If a supervertex has no subvertices then it will not be added to the graph. +-- +-- @param supervertex The supervertex which should be split. +-- +-- @param snapshots An array of snapshots at which the supervertex +-- should be split into a new one with the corresponding pgf-vertices. +-- If there are more than one snapshots passed to the function +-- for each snapshot there will be a new pseudo-vertex +-- +function Supergraph:splitSupervertex(supervertex, snapshots) + assert(supervertex, "no supervertex defined") + -- snapshots in temporal order + table.sort(snapshots, + function(s1,s2) + return s1.timestamp < s2.timestamp + end ) + + assert(#snapshots~=0) + + local edit_snapshots = supervertex.snapshots + local first_removed = math.huge + local rem_arcs = {} + for i = 1, #snapshots do + local s_first = self.snapshots_indices[snapshots[i]] + first_removed = math.min(s_first,first_removed) + local s_last + if i==#snapshots then + s_last = #self.snapshots + else + s_last = self.snapshots_indices[snapshots[i+1]]-1 + end + + local pseudovertex = Vertex.new { + kind = "super", + name = supervertex.name.."*"..i, + subvertex = {}, + snapshots = {} + } + + local has_subvertices = false + + for j = s_first, s_last do + local s = self.snapshots[j] + local vertex = self:getSnapshotVertex(supervertex, s) + if vertex then + self.supervertices[vertex] = pseudovertex + self:addSnapshotVertex(pseudovertex, s, vertex) + self:removeSnapshotVertex(supervertex, s) + + if not has_subvertices then + has_subvertices = true + self:add{pseudovertex} + end + + -- update edges: + local incoming = self.digraph:incoming(vertex) + local outgoing = self.digraph:outgoing(vertex) + + for _, arc in ipairs(incoming) do + local tail = self.supervertices[arc.tail] + local head = self.supervertices[arc.head] + self:assignToSuperarc(tail, pseudovertex, s) + + local super_arc = self:arc(tail, supervertex) + if not rem_arcs[super_arc] then + table.insert(rem_arcs, {arc = super_arc, snapshot = s}) + rem_arcs[super_arc] = true + end + end + + for _, arc in ipairs(outgoing) do + local tail = self.supervertices[arc.tail] + local head = self.supervertices[arc.head] + self:assignToSuperarc(pseudovertex, head, s) + + local super_arc = self:arc(supervertex, head) + if not rem_arcs[super_arc] then + table.insert(rem_arcs, {arc = super_arc, snapshot = s}) + rem_arcs[super_arc] = true + end + end + end + end + end + + if first_removed ~= math.huge then + for _, removed_arc in ipairs(rem_arcs) do + local snapshots = self.arc_snapshots[removed_arc.arc] + for i=#snapshots,1,-1 do + local s = snapshots[i] + if s.timestamp >= removed_arc.snapshot.timestamp then + table.remove(snapshots, i) + end + end + + if #snapshots==0 then + self:disconnect(removed_arc.arc.tail, removed_arc.arc.head) + end + end + end +end + +-- function Supergraph:reloadArcSnapshots() +-- for _, arc in ipairs(self.digraph.arcs) do +-- local snapshot = self:getSnapshot(arc.head) +-- local superarc = self:getSuperarc(arc) +-- texio.write("\n"..arc.tail.name..">"..arc.head.name) +-- self.arc_snapshots[superarc] = snapshot +-- end +-- end + +--- +-- Remove the binding of a vertex at a certain snapshot from its assigned +-- supervertex. +-- This requires time $O(n)$ where $n$ is the number of nodes actually +-- assigned to the supervertex. +function Supergraph:removeSnapshotVertex(supervertex, snapshot) + assert(supervertex and snapshot,"missing argument: the supervertex and snapshot must not be nil") + + -- remove reference to snapshot + for i = #supervertex.snapshots,1,-1 do + if supervertex.snapshots[i] == snapshot then + table.remove(supervertex.snapshots, i) + end + end + -- remove vertex at snapshot + supervertex.subvertex[snapshot] = nil +end + +--- +-- Assign a vertex to a snapshot vertex of this supergraph. +-- This requires time $O(1)$ +-- @param supervertex +-- +-- @param snapshot +-- +-- @param vertex The vertex which should be assigned to the supervertex +-- for the given snapshot. +-- +function Supergraph:addSnapshotVertex(supervertex, snapshot, vertex) + supervertex.subvertex[snapshot] = vertex + table.insert(supervertex.snapshots, snapshot) +end + +--- +-- Assign a given snapshot to the superarc between two supernodes. +-- If still no arc between those nodes exists a new edges will +-- be created. +-- This requires time $O(n)$ where $n$ is the number of snapshots already +-- assigned to the given arc. +-- +-- @param super_tail The tail of the directed arc in the supergraph. +-- +-- @param super_head The head of the directed arc in the supergraph. +-- +-- @param snapshot A snapshot in which both nodes are connected. +-- +-- @return The arc which was created or updated. +-- +function Supergraph:assignToSuperarc(super_tail, super_head, snapshot) + assert(self:contains(super_tail) and self:contains(super_head), + "tried to connect supernodes not in the supergraph") + + local super_arc = self:arc(super_tail, super_head) + if not super_arc then + super_arc = self:connect(super_tail, super_head) + end + + table.insert(self.arc_snapshots[super_arc], snapshot) + self.arc_snapshots[super_arc][snapshot] = true + + return super_arc +end + +return Supergraph + diff --git a/graphics/pgf/base/tex/generic/graphdrawing/lua/pgf/gd/experimental/evolving/SupergraphVertexSplitOptimization.lua b/graphics/pgf/base/tex/generic/graphdrawing/lua/pgf/gd/experimental/evolving/SupergraphVertexSplitOptimization.lua new file mode 100644 index 0000000000..b5a4ea22c9 --- /dev/null +++ b/graphics/pgf/base/tex/generic/graphdrawing/lua/pgf/gd/experimental/evolving/SupergraphVertexSplitOptimization.lua @@ -0,0 +1,196 @@ +-- Copyright 2015 by Malte Skambath +-- +-- This file may be distributed an/or modified +-- +-- 1. under the LaTeX Project Public License and/or +-- 2. under the GNU Public License +-- +-- See the file doc/generic/pgf/licenses/LICENSE for more information + + +local SupergraphVertexSplitOptimization = {} + +-- Imports +local lib = require "pgf.gd.lib" +local declare = require("pgf.gd.interface.InterfaceToAlgorithms").declare + +local Vertex = require "pgf.gd.model.Vertex" +local Digraph = require "pgf.gd.model.Digraph" +local Coordinate = require "pgf.gd.model.Coordinate" + +declare { + key = "split me", + type = "boolean", + initial = false +} + +declare { + key = "split on disappearing", + type = "boolean", + initial = true +} + +declare { + key = "split on disjoint neighbors", + type = "boolean", + initial = false +} + +declare { + key = "split on disjoint children", + type = "boolean", + initial = false +} + +declare { + key = "split on disjoint parents", + type = "boolean", + initial = false +} + +declare { + key = "split all supervertices", + type = "boolean", + initial = false +} + +declare { + key = "unbound vertex splitting", + algorithm = SupergraphVertexSplitOptimization, + phase = "supergraph optimization", + phase_default = true, + summary = [[" + Use this key if you want to disable animations. + Instead of producing animations the evolving graph animation phasephase animates all vertices including movements and + fade in or fade out animations. + "]], + documentation = [[" + See ToDo + "]] +} + + + +-- Help functions + + +-- Implementation + +function SupergraphVertexSplitOptimization:run() + local supergraph = assert(self.supergraph, "no supergraph passed") + + local split_on_dissapearing = self.digraph.options["split on disappearing"] + local split_on_no_common_neighbor = self.digraph.options["split on disjoint neighbors"] + local split_on_no_common_child = self.digraph.options["split on disjoint children"] + local split_on_no_common_parent = self.digraph.options["split on disjoint parents"] + local split_all = self.digraph.options["split all supervertices"] + + for _, supernode in ipairs(supergraph.vertices) do + -- follow trace of the supernode + local snapshots = supergraph:getSnapshots(supernode) + local splitsnapshots = {} + + for i=2, #snapshots do + local s = snapshots[i] + local s_prev = snapshots[i - 1] + local can_split = false + + if supergraph:consecutiveSnapshots(s_prev, s) then + local v1 = supergraph:getSnapshotVertex(supernode, s_prev) + local v2 = supergraph:getSnapshotVertex(supernode, s) + local is_child1 = {} + local is_parent1 = {} + local is_neighbor1 = {} + + local incoming1 = s_prev:incoming(v1) + local outgoing1 = s_prev:outgoing(v1) + + for _,e in ipairs(incoming1) do + local p = supergraph:getSupervertex(e.tail) + if p then + is_parent1[p] = true + is_neighbor1[p] = true + end + end + + for _,e in ipairs(outgoing1) do + local p = supergraph:getSupervertex(e.head) + if p then + is_child1[p] = true + is_neighbor1[p] = true + end + end + + local incoming2 = s:incoming(v2) + local outgoing2 = s:outgoing(v2) + + no_common_parent = true + no_common_child = true + no_common_neighbor = true + for _,e in ipairs(incoming2) do + local p = supergraph:getSupervertex(e.tail) + if p then + if is_neighbor1[p] then + no_common_neighbor = false + end + if is_parent1[p] then + no_common_parent = false + end + if (not no_common_neighbor) and (not no_common_parent) then + break + end + end + end + + for _,e in ipairs(outgoing2) do + local p = supergraph:getSupervertex(e.head) + if p then + if is_neighbor1[p] then + no_common_neighbor = false + end + if is_child1[p] then + no_common_child = false + end + if (not no_common_neighbor) and (not no_common_child) then + break + end + end + end + + + + if no_common_neighbor and split_on_no_common_neighbor then + can_split = true + --texio.write("[N@".. s.timestamp .."]") + end + if no_common_parent and split_on_no_common_parent then + can_split = true + --texio.write("[P@".. s.timestamp .."]") + end + if no_common_child and split_on_no_common_child then + can_split = true + --texio.write("[N@".. s.timestamp .."]") + end + if v2.options["split me"] then + can_split = true + end + else + can_split = true + --texio.write("[R@".. s.timestamp .."]") + end + if can_split or split_all then + table.insert(splitsnapshots, s) + end + end + if #splitsnapshots>0 then + supergraph:splitSupervertex(supernode, splitsnapshots) + end + end +end + + + + +-- Done + +return SupergraphVertexSplitOptimization diff --git a/graphics/pgf/base/tex/generic/graphdrawing/lua/pgf/gd/experimental/evolving/TimeSpec.lua b/graphics/pgf/base/tex/generic/graphdrawing/lua/pgf/gd/experimental/evolving/TimeSpec.lua new file mode 100644 index 0000000000..2b2168da1a --- /dev/null +++ b/graphics/pgf/base/tex/generic/graphdrawing/lua/pgf/gd/experimental/evolving/TimeSpec.lua @@ -0,0 +1,62 @@ +-- Copyright 2015 by Malte Skambath +-- +-- This file may be distributed and/or modified +-- +-- 1. under the LaTeX Project Public License and/or +-- 2. under the GNU Public License +-- +-- See the file doc/generic/pgf/licenses/LICENSE for more information + + +-- Imports +local declare = require("pgf.gd.interface.InterfaceToAlgorithms").declare + +--- + +declare { + key = "snapshot", + type = "time", + initial = "0s", + summary = "The time of the snapshot in which a PGF node should be visible.", + documentation = [[" + This option defines the time in seconds when respectively in which + state or snapshot of the graph the PGF represents a graph node. + "]], +} + +--- + +declare { + key = "supernode", + type = "string", + initial = "null", + summary = "A unique name for a node a given PGF node should be assigned to.", + documentation = [[" + Because it should be possible that nodes can change their + appearance, they are represented by separate PGF nodes in each + snapshot. To identify PGF nodes of the same supernode we have to + specify this key. + "]], +} + +--- + +declare { + key = "fadein time", + type = "time", + initial = "0.5s", + summary = [[" + The time in seconds it should take that a nodes will be fade in + when it disappears in the graph. + "]], +} + +--- + +declare { + key = "fadeout time", + type = "time", + initial = "0.5s", + summary = "", + documentation = "The same as |fadein time| but for disappearing nodes.", +} diff --git a/graphics/pgf/base/tex/generic/graphdrawing/lua/pgf/gd/experimental/evolving/doc.lua b/graphics/pgf/base/tex/generic/graphdrawing/lua/pgf/gd/experimental/evolving/doc.lua new file mode 100644 index 0000000000..dbd8146275 --- /dev/null +++ b/graphics/pgf/base/tex/generic/graphdrawing/lua/pgf/gd/experimental/evolving/doc.lua @@ -0,0 +1,120 @@ +-- Copyright 2012 by Malte Skambath +-- +-- This file may be distributed an/or modified +-- +-- 1. under the LaTeX Project Public License and/or +-- 2. under the GNU Public License +-- +-- See the file doc/generic/pgf/licenses/LICENSE for more information + +-- + + +local key = require 'pgf.gd.doc'.key +local documentation = require 'pgf.gd.doc'.documentation +local summary = require 'pgf.gd.doc'.summary +local example = require 'pgf.gd.doc'.example + + +-------------------------------------------------------------------- +key "animated tree layout" + +summary "This layout uses the Reingold--Tilform method for drawing trees." + +documentation +[[ +A method to create layouts for evolving graphs as an SVG animation.The Reingold--Tilford method is a standard method for drawing +trees. It is described in: + +The algorithm, which is based on the Reingold--Tilford algorithm and +its implementation in |graphdrawing.trees|, is introduced in my Masthesis: +% +\begin{itemize} + \item + M.\ Skambath, + \newblock Algorithmic Drawing of Evolving Trees, Masterthesis, 2016 +\end{itemize} + +You can use the same known graph macros as for other graph drawing +algorithms in Ti\emph{k}Z. In addition all keys and features that +are available for the static tree algorithm can be used: +% +\begin{codeexample}[animation list={1,1.5,2,2.5,3,3.5,4}] + \tikz \graph[animated binary tree layout, + nodes={draw,circle}, auto supernode, + ] { + {[when=1] 15 -> {10 -> { ,11}, 20 }}, + {[when=2] 15 -> {10 -> {3,11}, 20 }}, + {[when=3] 15 -> {10 -> {3, }, 20 }}, + {[when=4] 15 -> {10 -> {3, }, 20 -> 18 }}, + }; +\end{codeexample} +]] + + +example +[[ +\tikz[animated binary tree layout] + \graph[nodes={draw,circle}, auto supernode] { + {[when=1] 15 -> {10 -> { ,11}, 20 }}, + {[when=2] 15 -> {10 -> {3,11}, 20 }}, + {[when=3] 15 -> {10 -> {3, }, 20 }}, + {[when=4] 15 -> {10 -> {3, }, 20 -> 18 }}, + }; +]] +-------------------------------------------------------------------- + + + +-------------------------------------------------------------------- + +-------------------------------------------------------------------- + + + +-------------------------------------------------------------------- +key "animated binary tree layout" + +summary +[[ A layout based on the Reingold--Tilford method for drawing +binary trees. +]] + +documentation +[[ +This key executes: +% +\begin{enumerate} + \item |animated tree layout|, thereby selecting the Reingold--Tilford method, + \item |minimum number of children=2|, thereby ensuring the all nodes + have (at least) two children or none at all, and +\end{enumerate} +]] + + +example +[[ +]] + +example +[[ +]] +-------------------------------------------------------------------- + + + +-------------------------------------------------------------------- +key "extended animated binary tree layout" + +summary +[[ This algorithm is similar to |animated binary tree layout|, only the +option \texttt{missing nodes get space} is executed and the +\texttt{significant sep} is zero. +]] + +example +[[ +]] +-------------------------------------------------------------------- + + diff --git a/graphics/pgf/base/tex/generic/graphdrawing/lua/pgf/gd/experimental/evolving/layered.lua b/graphics/pgf/base/tex/generic/graphdrawing/lua/pgf/gd/experimental/evolving/layered.lua new file mode 100644 index 0000000000..15df9990f8 --- /dev/null +++ b/graphics/pgf/base/tex/generic/graphdrawing/lua/pgf/gd/experimental/evolving/layered.lua @@ -0,0 +1,107 @@ +-- Copyright 2012 by Till Tantau +-- Copyright 2015 by Malte Skambath +-- +-- This file may be distributed an/or modified +-- +-- 1. under the LaTeX Project Public License and/or +-- 2. under the GNU Public License +-- +-- See the file doc/generic/pgf/licenses/LICENSE for more information + + + + +local temporallayered = {} + +-- Namespace + +--require("pgf.gd").layered = layered +--require("pgf.gd.experimental.evolving").layered = layered + +-- Import +local lib = require "pgf.gd.lib" +local Storage = require "pgf.gd.lib.Storage" +local layered = require "pgf.gd.layered" + +-- +-- This file defines some basic functions to compute and/or set the +-- ideal distances between nodes of any kind of layered drawing of a +-- graph. + + + +--- +-- Position nodes in layers using baselines +-- +-- @param layers A |Storage| object assigning layers to vertices. +-- @param paddings A |Storage| object storing the computed distances +-- (paddings). +-- @param graph The graph in which the nodes reside +-- @param snapshots The list of snapshots over which the overlaying evolving +-- graph exists +function temporallayered.arrange_layers_by_baselines (layers, paddings, graph, snapshots, vertex_snapshots) + assert(vertex_snapshots, "vertex_snapshots must not be nil") + --local layer_vertices = Storage.newTableStorage() + local snapshots_layers = Storage.newTableStorage() + local count_layers = 0 + -- Decompose into layers: + for _,v in ipairs(graph.vertices) do + local layer_vertices = snapshots_layers[vertex_snapshots[v]] or {} + if layer_vertices[layers[v]] == nil then + assert( layers[v], "layer of node " .. v.name .. " has not been computed.") + layer_vertices[layers[v]] = {} + end + table.insert(layer_vertices[layers[v]], v) + count_layers = math.max(count_layers, layers[v]) + end + + if count_layers > 0 then + + + -- Now compute ideal distances and store + local height = 0 + + for _, s in ipairs(snapshots) do + local layer_vertices = snapshots_layers[s] + if #layer_vertices > 0 then -- sanity check + for _,v in ipairs(layer_vertices[1]) do + v.pos.y = 0 + end + end + end + + for i=2, count_layers do + local distance = 0 + for _, s in ipairs(snapshots) do + local layer_vertices = snapshots_layers[s] + if #layer_vertices >= i then + distance = math.max( + distance, + layered.baseline_distance( + paddings, + s, + layer_vertices[i-1], + layer_vertices[i])) + end + end + + height = height + distance + + for _, s in ipairs(snapshots) do + local layer_vertices = snapshots_layers[s] + if #layer_vertices >= i then + for _,v in ipairs(layer_vertices[i]) do + v.pos.y = height + end + end + end + end + end +end + + + + +-- Done + +return temporallayered diff --git a/graphics/pgf/base/tex/generic/graphdrawing/lua/pgf/gd/experimental/evolving/library.lua b/graphics/pgf/base/tex/generic/graphdrawing/lua/pgf/gd/experimental/evolving/library.lua new file mode 100644 index 0000000000..bb50eaf2cb --- /dev/null +++ b/graphics/pgf/base/tex/generic/graphdrawing/lua/pgf/gd/experimental/evolving/library.lua @@ -0,0 +1,33 @@ +-- Copyright 2016 by Malte Skambath +-- +-- This file may be distributed an/or modified +-- +-- 1. under the LaTeX Project Public License and/or +-- 2. under the GNU Public License +-- +-- See the file doc/generic/pgf/licenses/LICENSE for more information + + + +-- @library + +local evolving -- Library name + +-- Load dependencies: +require "pgf.gd.trees.ChildSpec" +require "pgf.gd.trees.ReingoldTilford1981" +require "pgf.gd.layered" + +-- Load declarations from: +require "pgf.gd.experimental.evolving.TimeSpec" +require "pgf.gd.experimental.evolving.Supergraph" + +-- Load preprocessing/optimization phases from: +require "pgf.gd.experimental.evolving.SupergraphVertexSplitOptimization" +require "pgf.gd.experimental.evolving.GreedyTemporalCycleRemoval" + +-- Load postprocessing/graph animation phases from: +require "pgf.gd.experimental.evolving.GraphAnimationCoordination" + +-- Load algorithms from: +require "pgf.gd.experimental.evolving.Skambath2016" diff --git a/graphics/pgf/base/tex/generic/graphdrawing/lua/pgf/gd/force.lua b/graphics/pgf/base/tex/generic/graphdrawing/lua/pgf/gd/force.lua new file mode 100644 index 0000000000..71efda1b35 --- /dev/null +++ b/graphics/pgf/base/tex/generic/graphdrawing/lua/pgf/gd/force.lua @@ -0,0 +1,26 @@ +-- Copyright 2012 by Till Tantau +-- +-- This file may be distributed an/or modified +-- +-- 1. under the LaTeX Project Public License and/or +-- 2. under the GNU Public License +-- +-- See the file doc/generic/pgf/licenses/LICENSE for more information + +--- @release $Header$ + + + +-- Imports + +require "pgf" +require "pgf.gd" + + +-- Declare namespace +pgf.gd.force = {} + + +-- Done + +return pgf.gd.force
\ No newline at end of file diff --git a/graphics/pgf/base/tex/generic/graphdrawing/lua/pgf/gd/force/CoarseGraph.lua b/graphics/pgf/base/tex/generic/graphdrawing/lua/pgf/gd/force/CoarseGraph.lua new file mode 100644 index 0000000000..a4e51dfeff --- /dev/null +++ b/graphics/pgf/base/tex/generic/graphdrawing/lua/pgf/gd/force/CoarseGraph.lua @@ -0,0 +1,435 @@ +-- Copyright 2011 by Jannis Pohlmann +-- Copyright 2012 by Till Tantau +-- +-- This file may be distributed an/or modified +-- +-- 1. under the LaTeX Project Public License and/or +-- 2. under the GNU Public License +-- +-- See the file doc/generic/pgf/licenses/LICENSE for more information + +-- @release $Header$ + + +--- A class for handling "coarse" versions of a graph. Such versions contain +-- less nodes and edges than the original graph while retaining the overall +-- structure. + +local Graph = require "pgf.gd.deprecated.Graph" -- we subclass from here +local CoarseGraph = Graph.new() +CoarseGraph.__index = CoarseGraph + + + +-- Namespace: +local force = require "pgf.gd.force" +force.CoarseGraph = CoarseGraph + + +-- Imports +local Node = require "pgf.gd.deprecated.Node" +local Edge = require "pgf.gd.deprecated.Edge" + +local lib = require "pgf.gd.lib" + + +-- Class setup + +CoarseGraph.COARSEN_INDEPENDENT_EDGES = 0 -- TT: Remark: These uppercase constants are *ugly*. Why do people do this?! +CoarseGraph.COARSEN_INDEPENDENT_NODES = 1 +CoarseGraph.COARSEN_HYBRID = 2 + + + +--- Creates a new coarse graph derived from an existing graph. +-- +-- Generates a coarse graph for the input |Graph|. +-- +-- Coarsening describes the process of reducing the amount of nodes in a graph +-- by merging nodes into supernodes. There are different strategies, called +-- schemes, that can be applied, like merging nodes that belong to edges in a +-- maximal independent edge set or by creating supernodes based on a maximal +-- independent node set. +-- +-- Coarsening is not performed automatically. The functions |CoarseGraph:coarsen| +-- and |CoarseGraph:interpolate| can be used to further coarsen the graph or +-- to restore the previous state (while interpolating the node positions from +-- the coarser version of the graph). +-- +-- Note, however, that the input \meta{graph} is always modified in-place, so +-- if the original version of \meta{graph} is needed in parallel to its +-- coarse representations, a deep copy of \meta{graph} needs to be passed over +-- to |CoarseGraph.new|. +-- +-- @param graph An existing graph that needs to be coarsened. +-- @param scheme Coarsening scheme to use. Possible values are:\par +-- |CoarseGraph.COARSEN_INDEPENDENT_EDGES|: +-- Coarsen the input graph by computing a maximal independent edge set +-- and collapsing edges from this set. The resulting coarse graph has +-- at least 50% of the nodes of the input graph. This coarsening scheme +-- gives slightly better results than +-- |CoarseGraph.COARSEN_INDEPENDENT_NODES| because it is less aggressive. +-- However, this comes at higher computational cost.\par +-- |CoarseGraph.COARSEN_INDEPENDENT_NODES|: +-- Coarsen the input graph by computing a maximal independent node set, +-- making nodes from this set supernodes in the coarse graph, merging +-- adjacent nodes into the supernodes and connecting the supernodes +-- if their graph distance is no greater than three. This scheme gives +-- slightly worse results than |CoarseGraph.COARSEN_INDEPENDENT_EDGES| +-- but is computationally more efficient.\par +-- |CoarseGraph.COARSEN_HYBRID|: Combines the other schemes by starting +-- with |CoarseGraph.COARSEN_INDEPENDENT_EDGES| and switching to +-- |CoarseGraph.COARSEN_INDEPENDENT_NODES| as soon as the first scheme +-- does not reduce the amount of nodes by a factor of 25%. +-- +function CoarseGraph.new(graph, scheme) + local coarse_graph = { + graph = graph, + level = 0, + scheme = scheme or CoarseGraph.COARSEN_INDEPENDENT_EDGES, + ratio = 0, + } + setmetatable(coarse_graph, CoarseGraph) + return coarse_graph +end + + + +local function custom_merge(table1, table2, first_metatable) + local result = table1 and lib.copy(table1) or {} + local first_metatable = first_metatable == true or false + + for key, value in pairs(table2) do + if not result[key] then + result[key] = value + end + end + + if not first_metatable or not getmetatable(result) then + setmetatable(result, getmetatable(table2)) + end + + return result +end + + +local function pairs_by_sorted_keys (t, f) + local a = {} + for n in pairs(t) do a[#a + 1] = n end + table.sort (a, f) + local i = 0 + return function () + i = i + 1 + return a[i], t[a[i]] + end +end + + + +function CoarseGraph:coarsen() + -- update the level + self.level = self.level + 1 + + local old_graph_size = #self.graph.nodes + + if self.scheme == CoarseGraph.COARSEN_INDEPENDENT_EDGES then + local matching, unmatched_nodes = self:findMaximalMatching() + + for _,edge in ipairs(matching) do + -- get the two nodes of the edge that we are about to collapse + local u, v = edge.nodes[1], edge.nodes[2] + + assert(u ~= v, 'the edge ' .. tostring(edge) .. ' is a loop. loops are not supported by this algorithm') + + -- create a supernode + local supernode = Node.new{ + name = '(' .. u.name .. ':' .. v.name .. ')', + weight = u.weight + v.weight, + subnodes = { u, v }, + subnode_edge = edge, + level = self.level, + } + + -- add the supernode to the graph + self.graph:addNode(supernode) + + -- collect all neighbors of the nodes to merge, create a node -> edge mapping + local u_neighbours = lib.map(u.edges, function(edge) return edge, edge:getNeighbour(u) end) + local v_neighbours = lib.map(v.edges, function(edge) return edge, edge:getNeighbour(v) end) + + -- remove the two nodes themselves from the neighbor lists + u_neighbours = lib.map(u_neighbours, function (edge,node) if node ~= v then return edge,node end end) + v_neighbours = lib.map(v_neighbours, function (edge,node) if node ~= u then return edge,node end end) + + -- compute a list of neighbors u and v have in common + local common_neighbours = lib.map(u_neighbours, + function (edge,node) + if v_neighbours[node] ~= nil then return edge,node end + end) + + -- create a node -> edges mapping for common neighbors + common_neighbours = lib.map(common_neighbours, function (edge, node) + return { edge, v_neighbours[node] }, node + end) + + -- drop common edges from the neighbor mappings + u_neighbours = lib.map(u_neighbours, function (val,node) if not common_neighbours[node] then return val,node end end) + v_neighbours = lib.map(v_neighbours, function (val,node) if not common_neighbours[node] then return val,node end end) + + -- merge neighbor lists + local disjoint_neighbours = custom_merge(u_neighbours, v_neighbours) + + -- create edges between the supernode and the neighbors of the merged nodes + for neighbour, edge in pairs_by_sorted_keys(disjoint_neighbours, function (n,m) return n.index < m.index end) do + + -- create a superedge to replace the existing one + local superedge = Edge.new{ + direction = edge.direction, + weight = edge.weight, + subedges = { edge }, + level = self.level, + } + + -- add the supernode and the neighbor to the edge + if u_neighbours[neighbour] then + superedge:addNode(neighbour) + superedge:addNode(supernode) + + else + superedge:addNode(supernode) + superedge:addNode(neighbour) + + end + + -- replace the old edge + self.graph:addEdge(superedge) + self.graph:deleteEdge(edge) + end + + -- do the same for all neighbors that the merged nodes have + -- in common, except that the weights of the new edges are the + -- sums of the of the weights of the edges to the common neighbors + for neighbour, edges in pairs_by_sorted_keys(common_neighbours, function (n,m) return n.index < m.index end) do + local weights = 0 + for _,e in ipairs(edges) do + weights = weights + edge.weight + end + + local superedge = Edge.new{ + direction = Edge.UNDIRECTED, + weight = weights, + subedges = edges, + level = self.level, + } + + -- add the supernode and the neighbor to the edge + superedge:addNode(supernode) + superedge:addNode(neighbour) + + -- replace the old edges + self.graph:addEdge(superedge) + for _,edge in ipairs(edges) do + self.graph:deleteEdge(edge) + end + end + + -- delete the nodes u and v which were replaced by the supernode + assert(#u.edges == 1, 'node ' .. u.name .. ' is part of a multiedge') -- if this fails, then there is a multiedge involving u + assert(#v.edges == 1, 'node ' .. v.name .. ' is part of a multiedge') -- same here + self.graph:deleteNode(u) + self.graph:deleteNode(v) + end + else + assert(false, 'schemes other than CoarseGraph.COARSEN_INDEPENDENT_EDGES are not implemented yet') + end + + -- calculate the number of nodes ratio compared to the previous graph + self.ratio = #self.graph.nodes / old_graph_size +end + + + +function CoarseGraph:revertSuperedge(superedge) + -- TODO we can probably skip adding edges that have one or more + -- subedges with the same level. But that needs more testing. + + -- TODO we might have to pass the corresponding supernode to + -- this method so that we can move subnodes to the same + -- position, right? Interpolating seems to work fine without + -- though... + + if #superedge.subedges == 1 then + local subedge = superedge.subedges[1] + + if not self.graph:findNode(subedge.nodes[1].name) then + self.graph:addNode(subedge.nodes[1]) + end + + if not self.graph:findNode(subedge.nodes[2].name) then + self.graph:addNode(subedge.nodes[2]) + end + + if not self.graph:findEdge(subedge) then + subedge.nodes[1]:addEdge(subedge) + subedge.nodes[2]:addEdge(subedge) + self.graph:addEdge(subedge) + end + + if subedge.level and subedge.level >= self.level then + self:revertSuperedge(subedge) + end + else + for _,subedge in ipairs(superedge.subedges) do + if not self.graph:findNode(subedge.nodes[1].name) then + self.graph:addNode(subedge.nodes[1]) + end + + if not self.graph:findNode(subedge.nodes[2].name) then + self.graph:addNode(subedge.nodes[2]) + end + + if not self.graph:findEdge(subedge) then + subedge.nodes[1]:addEdge(subedge) + subedge.nodes[2]:addEdge(subedge) + self.graph:addEdge(subedge) + end + + if subedge.level and subedge.level >= self.level then + self:revertSuperedge(subedge) + end + end + end +end + + + +function CoarseGraph:interpolate() + -- FIXME TODO Jannis: This does not work now that we allow multi-edges + -- and loops! Reverting generates the same edges multiple times which leads + -- to distorted drawings compared to the awesome results we had before! + + local nodes = lib.copy(self.graph.nodes) + + for _,supernode in ipairs(nodes) do + assert(not supernode.level or supernode.level <= self.level) + + if supernode.level and supernode.level == self.level then + -- move the subnode to the position of the supernode and add it to the graph + supernode.subnodes[1].pos.x = supernode.pos.x + supernode.subnodes[1].pos.y = supernode.pos.y + + if not self.graph:findNode(supernode.subnodes[1].name) then + self.graph:addNode(supernode.subnodes[1]) + end + + -- move the subnode to the position of the supernode and add it to the graph + supernode.subnodes[2].pos.x = supernode.pos.x + supernode.subnodes[2].pos.y = supernode.pos.y + + if not self.graph:findNode(supernode.subnodes[2].name) then + self.graph:addNode(supernode.subnodes[2]) + end + + if not self.graph:findEdge(supernode.subnode_edge) then + supernode.subnodes[1]:addEdge(supernode.subnode_edge) + supernode.subnodes[2]:addEdge(supernode.subnode_edge) + self.graph:addEdge(supernode.subnode_edge) + end + + local superedges = lib.copy(supernode.edges) + + for _,superedge in ipairs(superedges) do + self:revertSuperedge(superedge) + end + + self.graph:deleteNode(supernode) + end + end + + -- Make sure that the nodes and edges are in the correct order: + table.sort (self.graph.nodes, function (a, b) return a.index < b.index end) + table.sort (self.graph.edges, function (a, b) return a.index < b.index end) + for _, n in pairs(self.graph.nodes) do + table.sort (n.edges, function (a, b) return a.index < b.index end) + end + + -- update the level + self.level = self.level - 1 +end + + + +function CoarseGraph:getSize() + return #self.graph.nodes +end + + + +function CoarseGraph:getRatio() + return self.ratio +end + + + +function CoarseGraph:getLevel() + return self.level +end + + + +function CoarseGraph:getGraph() + return self.graph +end + + + +function CoarseGraph:findMaximalMatching() + local matching = {} + local matched_nodes = {} + local unmatched_nodes = {} + + -- iterate over nodes in random order + for _,j in ipairs(lib.random_permutation(#self.graph.nodes)) do + local node = self.graph.nodes[j] + -- ignore nodes that have already been matched + if not matched_nodes[node] then + -- mark the node as matched + matched_nodes[node] = true + + -- filter out edges adjacent to neighbors already matched + local edges = lib.imap(node.edges, + function (edge) + if not matched_nodes[edge:getNeighbour(node)] then return edge end + end) + + -- FIXME TODO We use a light-vertex matching here. This is + -- different from the algorithm proposed by Hu which collapses + -- edges based on a heavy-edge matching... + if #edges > 0 then + -- sort edges by the weights of the node's neighbors + table.sort(edges, function (a, b) + return a:getNeighbour(node).weight < b:getNeighbour(node).weight + end) + + -- match the node against the neighbor with minimum weight + matched_nodes[edges[1]:getNeighbour(node)] = true + table.insert(matching, edges[1]) + end + end + end + + -- generate a list of nodes that were not matched at all + for _,j in ipairs(lib.random_permutation(#self.graph.nodes)) do + local node = self.graph.nodes[j] + if not matched_nodes[node] then + table.insert(unmatched_nodes, node) + end + end + + return matching, unmatched_nodes +end + + +-- done + +return CoarseGraph diff --git a/graphics/pgf/base/tex/generic/graphdrawing/lua/pgf/gd/force/ControlCoarsening.lua b/graphics/pgf/base/tex/generic/graphdrawing/lua/pgf/gd/force/ControlCoarsening.lua new file mode 100644 index 0000000000..d69d5fc6c9 --- /dev/null +++ b/graphics/pgf/base/tex/generic/graphdrawing/lua/pgf/gd/force/ControlCoarsening.lua @@ -0,0 +1,148 @@ +-- Copyright 2012 by Till Tantau +-- +-- This file may be distributed an/or modified +-- +-- 1. under the LaTeX Project Public License and/or +-- 2. under the GNU Public License +-- +-- See the file doc/generic/pgf/licenses/LICENSE for more information + +-- @release $Header$ + + +-- Imports +local declare = require("pgf.gd.interface.InterfaceToAlgorithms").declare + + + +--- +-- @section subsubsection {Coarsening} +-- +-- @end + + +--- + +declare { + key = "coarsen", + type = "boolean", + initial = "true", + + summary = [[" + Defines whether or not a multilevel approach is used that + iteratively coarsens the input graph into graphs $G_1,\dots,G_l$ + with a smaller and smaller number of nodes. The coarsening stops as + soon as a minimum number of nodes is reached, as set via the + |minimum coarsening size| option, or if, in the last iteration, the + number of nodes was not reduced by at least the ratio specified via + |downsize ratio|. + "]], + documentation = [[" + A random initial layout is computed for the coarsest graph $G_l$ first. + Afterwards, it is laid out by computing the attractive and repulsive + forces between its nodes. + + In the subsequent steps, the previous coarse graph $G_{l-1}$ is + restored and its node positions are interpolated from the nodes + in~$G_l$. The graph $G_{l-1}$ is again laid out by computing the forces + between its nodes. These steps are repeated with $G_{l-2},\dots,G_1$ until + the original input graph $G_0$ has been restored, interpolated + and laid out. + + The idea behind this approach is that, by arranging recursively + formed supernodes first and then interpolating and arranging their + subnodes step by step, the algorithm is less likely to settle in a + local energy minimum (of which there can be many, particularly for + large graphs). The quality of the drawings with coarsening enabled is + expected to be higher than graphics where this feature is not applied. + + The following example demonstrates how coarsening can improve the + quality of graph drawings generated with Walshaw's algorihtm + |spring electrical layout'|. + "]], + examples = [[" + \tikz \graph [spring electrical layout', coarsen=false, vertical=3 to 4] + { + { [clique] 1, 2 } -- 3 -- 4 -- { 5, 6, 7 } + }; + + \tikz \graph [spring electrical layout', coarsen, vertical=3 to 4] + { + { [clique] 1, 2 } -- 3 -- 4 -- { 5, 6, 7 } + }; + "]] +} + +--- + +declare { + key = "minimum coarsening size", + type = "number", + initial = 2, + + summary = [[" + Defines the minimum number of nodes down to which the graph is + coarsened iteratively. The first graph that has a smaller or equal + number of nodes becomes the coarsest graph $G_l$, where $l$ is the + number of coarsening steps. The algorithm proceeds with the steps + described in the documentation of the |coarsen| option. + "]], + documentation = [[" + In the following example the same graph is coarsened down to two + and four nodes, respectively. The layout of the original graph is + interpolated from the random initial layout and is not improved + further because the forces are not computed (0 iterations). Thus, + in the two graphs, the nodes are placed at exactly two and four + coordinates in the final drawing. + "]], + examples = [[" + \tikz \graph [spring layout, iterations=0, + minimum coarsening size=2] + { subgraph C_n [n=8] }; + + \tikz \graph [spring layout, iterations=0, + minimum coarsening size=4] + { subgraph C_n [n=8] }; + "]] +} + +--- + +declare { + key = "downsize ratio", + type = "number", + initial = "0.25", + + summary = [[" + Minimum ratio between 0 and 1 by which the number of nodes between + two coarse graphs $G_i$ and $G_{i+1}$ need to be reduced in order for + the coarsening to stop and for the algorithm to use $G_{i+1}$ as the + coarsest graph $G_l$. Aside from the input graph, the optimal value + of |downsize ratio| mostly depends on the coarsening scheme being + used. Possible schemes are |collapse independent edges| and + |connect independent nodes|. + "]], + documentation = [[" + Increasing this option possibly reduces the number of coarse + graphs computed during the coarsening phase as coarsening will stop as + soon as a coarse graph does not reduce the number of nodes + substantially. This may speed up the algorithm but if the size of the + coarsest graph $G_l$ is much larger than |minimum coarsening size|, the + multilevel approach may not produce drawings as good as with a lower + |downsize ratio|. + "]], + examples = [[" + % 1. ratio too high, coarsening stops early, benefits are lost + \tikz \graph [spring electrical layout', + downsize ratio=1.0, + node distance=7mm, vertical=3 to 4] + { { [clique] 1, 2 } -- 3 -- 4 -- { 5, 6, 7 } }; + + % 2. ratio set to default, coarsening benefits are visible + \tikz \graph [spring electrical layout', + downsize ratio=0.2, + node distance=7mm, vertical=3 to 4] + { { [clique] 1, 2 } -- 3 -- 4 -- { 5, 6, 7 } }; + "]] +} + diff --git a/graphics/pgf/base/tex/generic/graphdrawing/lua/pgf/gd/force/ControlDeclare.lua b/graphics/pgf/base/tex/generic/graphdrawing/lua/pgf/gd/force/ControlDeclare.lua new file mode 100644 index 0000000000..af209d2c78 --- /dev/null +++ b/graphics/pgf/base/tex/generic/graphdrawing/lua/pgf/gd/force/ControlDeclare.lua @@ -0,0 +1,41 @@ +-- Copyright 2012 by Till Tantau +-- +-- This file may be distributed an/or modified +-- +-- 1. under the LaTeX Project Public License and/or +-- 2. under the GNU Public License +-- +-- See the file doc/generic/pgf/licenses/LICENSE for more information + +-- @release $Header$ + + +-- Imports +local declare = require("pgf.gd.interface.InterfaceToAlgorithms").declare + + +--- +-- @section subsection {Controlling and Configuring Force-Based Algorithms} +-- +-- All force-based algorithms are based on +-- a general pattern which we detail in the following. Numerous options +-- can be used to influence the behavior of this general pattern; more +-- specific options that apply only to individual algorithms are +-- explained along with these algorithms. +-- +-- The vertices are initially laid out in a random configuration. +-- Then the configuration is annealed to find a configuration of +-- minimal energy. To avoid getting stuck in a local minimum or at a +-- saddle point, random forces are added. All of this makes the final +-- layout extremely susceptible to changes in the random numbers. To +-- achieve a certain stability of the results, you should fix the +-- random seed. However, in the recent past Lua has switched its +-- random number generator, which means that you won't get the same +-- sequence of random numbers as in a previous version, even for +-- identical seed. If you rely on the long-term stability of vertex +-- placement, you should consider using a different layout. With the +-- spring layout you have to assume that the layout will be random. +-- +-- @end + + diff --git a/graphics/pgf/base/tex/generic/graphdrawing/lua/pgf/gd/force/ControlElectric.lua b/graphics/pgf/base/tex/generic/graphdrawing/lua/pgf/gd/force/ControlElectric.lua new file mode 100644 index 0000000000..c9f129c28d --- /dev/null +++ b/graphics/pgf/base/tex/generic/graphdrawing/lua/pgf/gd/force/ControlElectric.lua @@ -0,0 +1,105 @@ +-- Copyright 2012 by Till Tantau +-- +-- This file may be distributed an/or modified +-- +-- 1. under the LaTeX Project Public License and/or +-- 2. under the GNU Public License +-- +-- See the file doc/generic/pgf/licenses/LICENSE for more information + +-- @release $Header$ + + +-- Imports +local declare = require("pgf.gd.interface.InterfaceToAlgorithms").declare + + + +--- +-- @section subsubsection {Forces and Their Effects: Electrical +-- Repulsion} +-- +-- @end + + +--- + +declare { + key = "electric charge", + type = "number", + initial = 1, + + summary = [[" + Defines the electric charge of the node. The stronger the + |electric charge| of a node the stronger the repulsion between the + node and others in the graph. A negative |electric charge| means that + other nodes are further attracted to the node rather than repulsed, + although in theory this effect strongly depends on how the + |spring electrical layout| algorithm works. + Two typical effects of increasing the |electric charge| are distortion + of symmetries and an upscaling of the drawings. + "]], + examples = { + { + options = [["preamble={\usetikzlibrary{graphs,graphdrawing} \usegdlibrary{force}}"]], + code = [[" + \tikz \graph [spring electrical layout, horizontal=0 to 1] + { 0 [electric charge=1] -- subgraph C_n [n=10] }; + "]] + },{ + code = [[" + \tikz \graph [spring electrical layout, horizontal=0 to 1] + { 0 [electric charge=5] -- subgraph C_n [n=10] }; + "]] + },{ + code = [[" + \tikz \graph [spring electrical layout, horizontal=0 to 1] + { [clique] 1 [electric charge=5], 2, 3, 4 }; + "]] + } + } +} + + +--- + +declare { + key = "electric force order", + type = "number", + initial = "1", + + summary = [[" + Sometimes, when drawing symmetric and mesh-like graphs, the + peripheral distortion caused by long-range electric forces may be + undesired. Some electric force models allow to reduce long-range + forces and distortion effects by increasing + the order (exponent) of electric forces. Values between 0 and 1 + increase long-range electric forces and the scaling of the + generated layouts. Value greater than 1 decrease long-range + electric forces and results in shrinking drawings. + "]] + } + + +--- + +declare { + key = "approximate remote forces", + type = "boolean", + + summary = [[" + Force based algorithms often need to compute a force for each pair + of vertices, which, for larger numbers of vertices, can lead to a + significant time overhead. This problem can be addressed by + approximating these forces: For a vertex far removed from a cluster + of vertices, instead of computing the force contribution of each + vertex of the cluster individually, we form a sort of + ``supervertex'' at the ``gravitational center'' of the cluster and + then compute only the force between this supervertex and the single + vertex. + + \emph{Remark:} Currently, the implementation seems to be broken, at + least the results are somewhat strange when this key is used. + "]] + } + diff --git a/graphics/pgf/base/tex/generic/graphdrawing/lua/pgf/gd/force/ControlIteration.lua b/graphics/pgf/base/tex/generic/graphdrawing/lua/pgf/gd/force/ControlIteration.lua new file mode 100644 index 0000000000..b8d96630cd --- /dev/null +++ b/graphics/pgf/base/tex/generic/graphdrawing/lua/pgf/gd/force/ControlIteration.lua @@ -0,0 +1,136 @@ +-- Copyright 2012 by Till Tantau +-- +-- This file may be distributed an/or modified +-- +-- 1. under the LaTeX Project Public License and/or +-- 2. under the GNU Public License +-- +-- See the file doc/generic/pgf/licenses/LICENSE for more information + +-- @release $Header$ + + +-- Imports +local declare = require("pgf.gd.interface.InterfaceToAlgorithms").declare + + + + +--- +-- @section subsubsection {The Iterative Process and Cooling} +-- +-- @end + + + +--- + +declare { + key = "iterations", + type = "number", + initial = "500", + + summary = [[" + Limits the number of iterations of algorithms for force-based + layouts to \meta{number}. + "]], + documentation = [[" + Depending on the characteristics of the input graph and the parameters + chosen for the algorithm, minimizing the system energy may require + many iterations. + + In these situations it may come in handy to limit the number of + iterations. This feature can also be useful to draw the same graph + after different iterations and thereby demonstrate how the spring or + spring-electrical algorithm improves the drawing step by step. + + The examples shows two drawings generated using two + different |iteration| limits. + "]], + examples = {[[" + \tikz \graph [spring layout, iterations=10] { subgraph K_n [n=4] }; + "]],[[" + \tikz \graph [spring layout, iterations=500] { subgraph K_n [n=4] }; + "]],[[" + \tikz \graph [spring electrical layout, iterations=10] + { subgraph K_n [n=4] }; + "]],[[" + \tikz \graph [spring electrical layout, iterations=500] + { subgraph K_n [n=4] }; + "]] + } +} + +--- + +declare { + key = "initial step length", + type = "length", + initial = "0", + + summary = [[" + This parameter specifies the amount by which nodes will be + displaced in each iteration, initially. If set to |0| (which is the + default), an appropriate value is computed automatically. + "]] + } + +--- + +declare { + key = "cooling factor", + type = "number", + initial = "0.95", + + summary = [[" + This parameter helps in controlling how layouts evolve over + time. It is used to gradually reduce the step size + between one iteration to the next. + "]], + documentation = [[" + A small positive cooling factor + $\ge 0$ means that the movement of nodes is quickly or abruptly + reduced, while a large cooling factor $\le 1$ allows for a smoother + step by step layout refinement at the cost of more iterations. The + following example demonstrates how a smaller cooling factor may + result in a less balanced drawing. By default, Hu2006 spring, + Hu2006 spring electrical, and Walshaw2000 spring electrical use a + cooling factor of |0.95|. + "]], + examples = {[[" + \tikz \graph [spring layout, cooling factor=0.1] + { a -> b -> c -> a }; + "]],[[" + \tikz \graph [spring layout, cooling factor=0.5] + { a -> b -> c -> a }; + "]] + } +} + +--- + +declare { + key = "convergence tolerance", + type = "number", + initial = "0.01", + + summary = [[" + All spring and spring-electrical algorithms implemented in the + thesis terminate as soon as the maximum movement of any node drops + below $k \cdot \meta{tolerance}$. This tolerance factor can be changed + with the convergence tolerance option: + "]], + examples = {[[" + \tikz \graph [spring layout, convergence tolerance=0.001] + { { [clique] 1, 2 } -- 3 -- 4 -- { 5, 6, 7 } }; + "]],[[" + \tikz \graph [spring layout, convergence tolerance=1.0] + { { [clique] 1, 2 } -- 3 -- 4 -- { 5, 6, 7 } }; + "]] + } +} + + + + + diff --git a/graphics/pgf/base/tex/generic/graphdrawing/lua/pgf/gd/force/ControlSprings.lua b/graphics/pgf/base/tex/generic/graphdrawing/lua/pgf/gd/force/ControlSprings.lua new file mode 100644 index 0000000000..9b0c1071e3 --- /dev/null +++ b/graphics/pgf/base/tex/generic/graphdrawing/lua/pgf/gd/force/ControlSprings.lua @@ -0,0 +1,59 @@ +-- Copyright 2012 by Till Tantau +-- +-- This file may be distributed an/or modified +-- +-- 1. under the LaTeX Project Public License and/or +-- 2. under the GNU Public License +-- +-- See the file doc/generic/pgf/licenses/LICENSE for more information + +-- @release $Header$ + + +-- Imports +local declare = require("pgf.gd.interface.InterfaceToAlgorithms").declare + + +--- +-- @section subsubsection {Forces and Their Effects: Springs} +-- +-- The most important parameter of springs is their ``natural +-- length'', which can be configured using the general-purpose +-- |node distance| parameter. It is the ``equilibrium length'' of a +-- spring between two nodes in the graph. When an edge has this +-- length, no forces will ``push'' or ``pull'' along the edge. +-- +-- The following examples shows how a simple graph can be scaled by +-- changing the |node distance|: +-- % +-- \begin{codeexample}[preamble={\usetikzlibrary{graphs.standard,graphdrawing} +-- \usegdlibrary{force}}] +-- \tikz \graph [spring layout, node distance=7mm] { subgraph C_n[n=3] }; +-- \tikz \graph [spring layout] { subgraph C_n[n=3] }; +-- \tikz \graph [spring layout, node distance=15mm]{ subgraph C_n[n=3] }; +-- \end{codeexample} +-- % +-- \begin{codeexample}[preamble={\usetikzlibrary{graphs.standard,graphdrawing} +-- \usegdlibrary{force}}] +-- \tikz \graph [spring electrical layout, node distance=0.7cm] { subgraph C_n[n=3] }; +-- \tikz \graph [spring electrical layout] { subgraph C_n[n=3] }; +-- \tikz \graph [spring electrical layout, node distance=1.5cm] { subgraph C_n[n=3] }; +-- \end{codeexample} +-- +-- @end + + +--- + +declare { + key = "spring constant", + type = "number", + initial = "0.01", + + summary = [[" + The ``spring constant'' is a factor from Hooke's law describing the + ``stiffness'' of a spring. This factor is used inside spring-based + algorithms to determine how strongly edges ``pull'' and ``push'' at + the nodes they connect. + "]] +} diff --git a/graphics/pgf/base/tex/generic/graphdrawing/lua/pgf/gd/force/ControlStart.lua b/graphics/pgf/base/tex/generic/graphdrawing/lua/pgf/gd/force/ControlStart.lua new file mode 100644 index 0000000000..85a64b0a5a --- /dev/null +++ b/graphics/pgf/base/tex/generic/graphdrawing/lua/pgf/gd/force/ControlStart.lua @@ -0,0 +1,41 @@ +-- Copyright 2012 by Till Tantau +-- +-- This file may be distributed an/or modified +-- +-- 1. under the LaTeX Project Public License and/or +-- 2. under the GNU Public License +-- +-- See the file doc/generic/pgf/licenses/LICENSE for more information + +-- @release $Header$ + + +-- Imports +local declare = require("pgf.gd.interface.InterfaceToAlgorithms").declare + + +--- +-- @section subsubsection {Start Configuration} +-- +-- Currently, the start configuration for force-based algorithms is a +-- random distribution of the vertices. You can influence it by +-- changing the |random seed|: +-- % +-- \begin{codeexample}[preamble={\usetikzlibrary{graphs,graphdrawing} +-- \usegdlibrary{force}}] +-- \tikz \graph [random seed=10, spring layout] { +-- a -- {b, c, d} -- e -- f -- {g,h} -- {a,b,e}; +-- }; +-- \end{codeexample} +-- % +-- \begin{codeexample}[preamble={\usetikzlibrary{graphs,graphdrawing} +-- \usegdlibrary{force}}] +-- \tikz \graph [random seed=11, spring layout] { +-- a -- {b, c, d} -- e -- f -- {g,h} -- {a,b,e}; +-- }; +-- \end{codeexample} +-- +-- Other methods, like a planar preembedding, are not implemented +-- currently. +-- +-- @end diff --git a/graphics/pgf/base/tex/generic/graphdrawing/lua/pgf/gd/force/QuadTree.lua b/graphics/pgf/base/tex/generic/graphdrawing/lua/pgf/gd/force/QuadTree.lua new file mode 100644 index 0000000000..3e1620dc21 --- /dev/null +++ b/graphics/pgf/base/tex/generic/graphdrawing/lua/pgf/gd/force/QuadTree.lua @@ -0,0 +1,280 @@ +-- Copyright 2011 by Jannis Pohlmann +-- Copyright 2012 by Till Tantau +-- +-- This file may be distributed an/or modified +-- +-- 1. under the LaTeX Project Public License and/or +-- 2. under the GNU Public License +-- +-- See the file doc/generic/pgf/licenses/LICENSE for more information + +-- @release $Header$ + + +--- An implementation of a quad trees. +-- +-- The class QuadTree provides methods form handling quadtrees. +-- + +local QuadTree = { + -- Subclasses + Particle = {}, + Cell = {} +} +QuadTree.__index = QuadTree + +-- Namespace: +require("pgf.gd.force").QuadTree = QuadTree + +-- Imports: +local Vector = require "pgf.gd.deprecated.Vector" +local lib = require "pgf.gd.lib" + + +--- Creates a new quad tree. +-- +-- @return A newly-allocated quad tree. +-- +function QuadTree.new(x, y, width, height, max_particles) + local tree = { + root_cell = QuadTree.Cell.new(x, y, width, height, max_particles) + } + setmetatable(tree, QuadTree) + return tree +end + + + +--- Inserts a particle +-- +-- @param param A particle of type QuadTree.Particle +-- +function QuadTree:insert(particle) + self.root_cell:insert(particle) +end + + + +--- Computes the interactions of a particle with other cells +-- +-- @param particle A particle +-- @param test_func A test function, which on input of a cubical cell and a particle should +-- decide whether the cubical cell should be inserted into the result +-- @param cells An optional array of cells, to which the found cells will be added +-- +-- @return The cells array or a new array, if it was empty. +-- +function QuadTree:findInteractionCells(particle, test_func, cells) + local test_func = test_func or function (cell, particle) return true end + cells = cells or {} + + self.root_cell:findInteractionCells(particle, test_func, cells) + + return cells +end + + + + +--- Particle subclass +QuadTree.Particle.__index = QuadTree.Particle + + + +--- Creates a new particle. +-- +-- @return A newly-allocated particle. +-- +function QuadTree.Particle.new(pos, mass) + local particle = { + pos = pos:copy(), + mass = mass or 1, + subparticles = {}, + } + setmetatable(particle, QuadTree.Particle) + return particle +end + + + +--- A cell of a quadtree +-- +-- TT: Why is it called "cubical", by the way?! + +QuadTree.Cell.__index = QuadTree.Cell + + + +--- Creates a new cubicle cell. +-- +-- @return a newly-allocated cubicle cell. +-- +function QuadTree.Cell.new(x, y, width, height, max_particles) + local cell = { + x = x, + y = y, + width = width, + height = height, + max_particles = max_particles or 1, + subcells = {}, + particles = {}, + center_of_mass = nil, + mass = 0, + } + setmetatable(cell, QuadTree.Cell) + return cell +end + + + +function QuadTree.Cell:containsParticle(particle) + return particle.pos.x >= self.x and particle.pos.x <= self.x + self.width + and particle.pos.y >= self.y and particle.pos.y <= self.y + self.height +end + + + +function QuadTree.Cell:findSubcell(particle) + return lib.find(self.subcells, function (cell) + return cell:containsParticle(particle) + end) +end + + + +function QuadTree.Cell:createSubcells() + assert(type(self.subcells) == 'table' and #self.subcells == 0) + assert(type(self.particles) == 'table' and #self.particles <= self.max_particles) + + if #self.subcells == 0 then + for _,x in ipairs({self.x, self.x + self.width/2}) do + for _,y in ipairs({self.y, self.y + self.height/2}) do + local cell = QuadTree.Cell.new(x, y, self.width/2, self.height/2, self.max_particles) + table.insert(self.subcells, cell) + end + end + end +end + + + +function QuadTree.Cell:insert(particle) + -- check if we have a particle with the exact same position already + local existing = lib.find(self.particles, function (other) + return other.pos:equals(particle.pos) + end) + + if existing then + -- we already have a particle at the same position; splitting the cell + -- up makes no sense; instead we add the new particle as a + -- subparticle of the existing one + table.insert(existing.subparticles, particle) + else + if #self.subcells == 0 and #self.particles < self.max_particles then + table.insert(self.particles, particle) + else + if #self.subcells == 0 then + self:createSubcells() + end + + -- move particles to the new subcells + for _,existing in ipairs(self.particles) do + local cell = self:findSubcell(existing) + assert(cell, 'failed to find a cell for particle ' .. tostring(existing.pos)) + cell:insert(existing) + end + + self.particles = {} + + local cell = self:findSubcell(particle) + assert(cell) + cell:insert(particle) + end + end + + self:updateMass() + self:updateCenterOfMass() + + assert(self.mass) + assert(self.center_of_mass) +end + + + +function QuadTree.Cell:updateMass() + -- reset mass to zero + self.mass = 0 + + if #self.subcells == 0 then + -- the mass is the number of particles of the cell + for _,particle in ipairs(self.particles) do + self.mass = self.mass + particle.mass + for _,subparticle in ipairs(particle.subparticles) do + self.mass = self.mass + subparticle.mass + end + end + else + -- the mass is the sum of the masses of the subcells + for _,subcell in ipairs(self.subcells) do + self.mass = self.mass + subcell.mass + end + end +end + + + +function QuadTree.Cell:updateCenterOfMass() + -- reset center of mass, assuming the cell is empty + self.center_of_mass = nil + + if #self.subcells == 0 then + -- the center of mass is the average position of the particles + -- weighted by their masses + self.center_of_mass = Vector.new (2) + for _,p in ipairs(self.particles) do + for _,sp in ipairs(p.subparticles) do + self.center_of_mass = self.center_of_mass:plus(sp.pos:timesScalar(sp.mass)) + end + self.center_of_mass = self.center_of_mass:plus(p.pos:timesScalar(p.mass)) + end + self.center_of_mass = self.center_of_mass:dividedByScalar(self.mass) + else + -- the center of mass is the average of the weighted centers of mass + -- of the subcells + self.center_of_mass = Vector.new(2) + for _,sc in ipairs(self.subcells) do + if sc.center_of_mass then + self.center_of_mass = self.center_of_mass:plus(sc.center_of_mass:timesScalar(sc.mass)) + else + assert(sc.mass == 0) + end + end + self.center_of_mass = self.center_of_mass:dividedByScalar(self.mass) + end +end + + + +function QuadTree.Cell:findInteractionCells(particle, test_func, cells) + if #self.subcells == 0 or test_func(self, particle) then + table.insert(cells, self) + else + for _,subcell in ipairs(self.subcells) do + subcell:findInteractionCells(particle, test_func, cells) + end + end +end + + +function QuadTree.Cell:__tostring() + return '((' .. self.x .. ', ' .. self.y .. ') ' + .. 'to (' .. self.x + self.width .. ', ' .. self.y + self.height .. '))' + .. (self.particle and ' => ' .. self.particle.name or '') + .. (self.center_of_mass and ' mass ' .. self.mass .. ' at ' .. tostring(self.center_of_mass) or '') +end + + + +-- done + +return QuadTree diff --git a/graphics/pgf/base/tex/generic/graphdrawing/lua/pgf/gd/force/SpringElectricalHu2006.lua b/graphics/pgf/base/tex/generic/graphdrawing/lua/pgf/gd/force/SpringElectricalHu2006.lua new file mode 100644 index 0000000000..a7230eb1f6 --- /dev/null +++ b/graphics/pgf/base/tex/generic/graphdrawing/lua/pgf/gd/force/SpringElectricalHu2006.lua @@ -0,0 +1,633 @@ +-- Copyright 2011 by Jannis Pohlmann +-- +-- This file may be distributed and/or modified +-- +-- 1. under the LaTeX Project Public License and/or +-- 2. under the GNU Public License +-- +-- See the file doc/generic/pgf/licenses/LICENSE for more information + +-- @release $Header$ + + +local SpringElectricalHu2006 = {} + +-- Imports +local declare = require("pgf.gd.interface.InterfaceToAlgorithms").declare + + + + +--- + +declare { + key = "spring electrical Hu 2006 layout", + algorithm = SpringElectricalHu2006, + + preconditions = { + connected = true, + loop_free = true, + simple = true, + }, + + old_graph_model = true, + + summary = [[" + Implementation of a spring electrical graph drawing algorithm based on + a paper by Hu. + "]], + documentation = [[" + \begin{itemize} + \item + Y. Hu. + \newblock Efficient, high-quality force-directed graph drawing. + \newblock \emph{The Mathematica Journal}, 2006. + \end{itemize} + + There are some modifications compared to the original algorithm, + see the Diploma thesis of Pohlmann for details. + "]] +} + +-- Imports + +local PathLengths = require "pgf.gd.lib.PathLengths" +local Vector = require "pgf.gd.deprecated.Vector" + +local QuadTree = require "pgf.gd.force.QuadTree" +local CoarseGraph = require "pgf.gd.force.CoarseGraph" + +local lib = require "pgf.gd.lib" + + +function SpringElectricalHu2006:run() + + -- Setup properties + local options = self.digraph.options + + self.iterations = options['iterations'] + self.cooling_factor = options['cooling factor'] + self.initial_step_length = options['initial step length'] + self.convergence_tolerance = options['convergence tolerance'] + + self.natural_spring_length = options['node distance'] + self.spring_constant = options['spring constant'] + + self.approximate_repulsive_forces = options['approximate remote forces'] + self.repulsive_force_order = options['electric force order'] + + self.coarsen = options['coarsen'] + self.downsize_ratio = options['downsize ratio'] + self.minimum_graph_size = options['minimum coarsening size'] + + -- Adjust types + self.downsize_ratio = math.max(0, math.min(1, self.downsize_ratio)) + self.graph_size = #self.graph.nodes + self.graph_density = (2 * #self.graph.edges) / (#self.graph.nodes * (#self.graph.nodes - 1)) + + -- validate input parameters + assert(self.iterations >= 0, 'iterations (value: ' .. self.iterations .. ') need to be greater than 0') + assert(self.cooling_factor >= 0 and self.cooling_factor <= 1, 'the cooling factor (value: ' .. self.cooling_factor .. ') needs to be between 0 and 1') + assert(self.initial_step_length >= 0, 'the initial step length (value: ' .. self.initial_step_length .. ') needs to be greater than or equal to 0') + assert(self.convergence_tolerance >= 0, 'the convergence tolerance (value: ' .. self.convergence_tolerance .. ') needs to be greater than or equal to 0') + assert(self.natural_spring_length >= 0, 'the natural spring dimension (value: ' .. self.natural_spring_length .. ') needs to be greater than or equal to 0') + assert(self.spring_constant >= 0, 'the spring constant (value: ' .. self.spring_constant .. ') needs to be greater or equal to 0') + assert(self.downsize_ratio >= 0 and self.downsize_ratio <= 1, 'the downsize ratio (value: ' .. self.downsize_ratio .. ') needs to be between 0 and 1') + assert(self.minimum_graph_size >= 2, 'the minimum coarsening size of coarse graphs (value: ' .. self.minimum_graph_size .. ') needs to be greater than or equal to 2') + + -- initialize node weights + for _,node in ipairs(self.graph.nodes) do + if node:getOption('electric charge') ~= nil then + node.weight = node:getOption('electric charge') + else + node.weight = 1 + end + end + + -- initialize edge weights + for _,edge in ipairs(self.graph.edges) do + edge.weight = 1 + end + + -- initialize the coarse graph data structure. note that the algorithm + -- is the same regardless whether coarsening is used, except that the + -- number of coarsening steps without coarsening is 0 + local coarse_graph = CoarseGraph.new(self.graph) + + -- check if the multilevel approach should be used + if self.coarsen then + -- coarsen the graph repeatedly until only minimum_graph_size nodes + -- are left or until the size of the coarse graph was not reduced by + -- at least the downsize ratio configured by the user + while coarse_graph:getSize() > self.minimum_graph_size + and coarse_graph:getRatio() <= (1 - self.downsize_ratio) + do + coarse_graph:coarsen() + end + end + + if self.coarsen then + -- use the natural spring length as the initial natural spring length + local spring_length = self.natural_spring_length + + -- compute a random initial layout for the coarsest graph + self:computeInitialLayout(coarse_graph.graph, spring_length) + + -- set the spring length to the average edge length of the initial layout + spring_length = 0 + for _,edge in ipairs(coarse_graph.graph.edges) do + spring_length = spring_length + edge.nodes[1].pos:minus(edge.nodes[2].pos):norm() + end + spring_length = spring_length / #coarse_graph.graph.edges + + -- additionally improve the layout with the force-based algorithm + -- if there are more than two nodes in the coarsest graph + if coarse_graph:getSize() > 2 then + self:computeForceLayout(coarse_graph.graph, spring_length, SpringElectricalHu2006.adaptive_step_update) + end + + -- undo coarsening step by step, applying the force-based sub-algorithm + -- to every intermediate coarse graph as well as the original graph + while coarse_graph:getLevel() > 0 do + + -- compute the diameter of the parent coarse graph + local parent_diameter = PathLengths.pseudoDiameter(coarse_graph.graph) + + -- interpolate the previous coarse graph from its parent + coarse_graph:interpolate() + + -- compute the diameter of the current coarse graph + local current_diameter = PathLengths.pseudoDiameter(coarse_graph.graph) + + -- scale node positions by the quotient of the pseudo diameters + for _,node in ipairs(coarse_graph.graph) do + node.pos:update(function (n, value) + return value * (current_diameter / parent_diameter) + end) + end + + -- compute forces in the graph + self:computeForceLayout(coarse_graph.graph, spring_length, SpringElectricalHu2006.conservative_step_update) + end + else + -- compute a random initial layout for the coarsest graph + self:computeInitialLayout(coarse_graph.graph, self.natural_spring_length) + + -- set the spring length to the average edge length of the initial layout + spring_length = 0 + for _,edge in ipairs(coarse_graph.graph.edges) do + spring_length = spring_length + edge.nodes[1].pos:minus(edge.nodes[2].pos):norm() + end + spring_length = spring_length / #coarse_graph.graph.edges + + -- improve the layout with the force-based algorithm + self:computeForceLayout(coarse_graph.graph, spring_length, SpringElectricalHu2006.adaptive_step_update) + end +end + + + +function SpringElectricalHu2006:computeInitialLayout(graph, spring_length) + -- TODO how can supernodes and fixed nodes go hand in hand? + -- maybe fix the supernode if at least one of its subnodes is + -- fixated? + + -- fixate all nodes that have a 'desired at' option. this will set the + -- node.fixed member to true and also set node.pos.x and node.pos.y + self:fixateNodes(graph) + + if #graph.nodes == 2 then + if not (graph.nodes[1].fixed and graph.nodes[2].fixed) then + local fixed_index = graph.nodes[2].fixed and 2 or 1 + local loose_index = graph.nodes[2].fixed and 1 or 2 + + if not graph.nodes[1].fixed and not graph.nodes[2].fixed then + -- both nodes can be moved, so we assume node 1 is fixed at (0,0) + graph.nodes[1].pos.x = 0 + graph.nodes[1].pos.y = 0 + end + + -- position the loose node relative to the fixed node, with + -- the displacement (random direction) matching the spring length + local direction = Vector.new{x = lib.random(1, spring_length), y = lib.random(1, spring_length)} + local distance = 3 * spring_length * self.graph_density * math.sqrt(self.graph_size) / 2 + local displacement = direction:normalized():timesScalar(distance) + + graph.nodes[loose_index].pos = graph.nodes[fixed_index].pos:plus(displacement) + else + -- both nodes are fixed, initial layout may be far from optimal + end + else + + -- use a random positioning technique + local function positioning_func(n) + local radius = 3 * spring_length * self.graph_density * math.sqrt(self.graph_size) / 2 + return lib.random(-radius, radius) + end + + -- compute initial layout based on the random positioning technique + for _,node in ipairs(graph.nodes) do + if not node.fixed then + node.pos.x = positioning_func(1) + node.pos.y = positioning_func(2) + end + end + end +end + + + +function SpringElectricalHu2006:computeForceLayout(graph, spring_length, step_update_func) + -- global (=repulsive) force function + function accurate_repulsive_force(distance, weight) + -- note: the weight is taken into the equation here. unlike in the original + -- algorithm different electric charges are allowed for each node in this + -- implementation + return - weight * self.spring_constant * math.pow(spring_length, self.repulsive_force_order + 1) / math.pow(distance, self.repulsive_force_order) + end + + -- global (=repulsive, approximated) force function + function approximated_repulsive_force(distance, mass) + return - mass * self.spring_constant * math.pow(spring_length, self.repulsive_force_order + 1) / math.pow(distance, self.repulsive_force_order) + end + + -- local (spring) force function + function attractive_force(distance) + return (distance * distance) / spring_length + end + + -- define the Barnes-Hut opening criterion + function barnes_hut_criterion(cell, particle) + local distance = particle.pos:minus(cell.center_of_mass):norm() + return cell.width / distance <= 1.2 + end + + -- fixate all nodes that have a 'desired at' option. this will set the + -- node.fixed member to true and also set node.pos.x and node.pos.y + self:fixateNodes(graph) + + -- adjust the initial step length automatically if desired by the user + local step_length = self.initial_step_length == 0 and spring_length or self.initial_step_length + + -- convergence criteria etc. + local converged = false + local energy = math.huge + local iteration = 0 + local progress = 0 + + while not converged and iteration < self.iterations do + -- remember old node positions + local old_positions = lib.map(graph.nodes, function (node) + return node.pos:copy(), node + end) + + -- remember the old system energy and reset it for the current iteration + local old_energy = energy + energy = 0 + + -- build the quadtree for approximating repulsive forces, if desired + local quadtree = nil + if self.approximate_repulsive_forces then + quadtree = self:buildQuadtree(graph) + end + + for _,v in ipairs(graph.nodes) do + if not v.fixed then + -- vector for the displacement of v + local d = Vector.new(2) + + -- compute repulsive forces + if self.approximate_repulsive_forces then + -- determine the cells that have a repulsive influence on v + local cells = quadtree:findInteractionCells(v, barnes_hut_criterion) + + -- compute the repulsive force between these cells and v + for _,cell in ipairs(cells) do + -- check if the cell is a leaf + if #cell.subcells == 0 then + -- compute the forces between the node and all particles in the cell + for _,particle in ipairs(cell.particles) do + local real_particles = lib.copy(particle.subparticles) + table.insert(real_particles, particle) + + for _,real_particle in ipairs(real_particles) do + local delta = real_particle.pos:minus(v.pos) + + -- enforce a small virtual distance if the node and the cell's + -- center of mass are located at (almost) the same position + if delta:norm() < 0.1 then + delta:update(function (n, value) return 0.1 + lib.random() * 0.1 end) + end + + -- compute the repulsive force vector + local repulsive_force = approximated_repulsive_force(delta:norm(), real_particle.mass) + local force = delta:normalized():timesScalar(repulsive_force) + + -- move the node v accordingly + d = d:plus(force) + end + end + else + -- compute the distance between the node and the cell's center of mass + local delta = cell.center_of_mass:minus(v.pos) + + -- enforce a small virtual distance if the node and the cell's + -- center of mass are located at (almost) the same position + if delta:norm() < 0.1 then + delta:update(function (n, value) return 0.1 + lib.random() * 0.1 end) + end + + -- compute the repulsive force vector + local repulsive_force = approximated_repulsive_force(delta:norm(), cell.mass) + local force = delta:normalized():timesScalar(repulsive_force) + + -- move the node v accordingly + d = d:plus(force) + end + end + else + for _,u in ipairs(graph.nodes) do + if v ~= u then + -- compute the distance between u and v + local delta = u.pos:minus(v.pos) + + -- enforce a small virtual distance if the nodes are + -- located at (almost) the same position + if delta:norm() < 0.1 then + delta:update(function (n, value) return 0.1 + lib.random() * 0.1 end) + end + + -- compute the repulsive force vector + local repulsive_force = accurate_repulsive_force(delta:norm(), u.weight) + local force = delta:normalized():timesScalar(repulsive_force) + + -- move the node v accordingly + d = d:plus(force) + end + end + end + + -- compute attractive forces between v and its neighbors + for _,edge in ipairs(v.edges) do + local u = edge:getNeighbour(v) + + -- compute the distance between u and v + local delta = u.pos:minus(v.pos) + + -- enforce a small virtual distance if the nodes are + -- located at (almost) the same position + if delta:norm() < 0.1 then + delta:update(function (n, value) return 0.1 + lib.random() * 0.1 end) + end + + -- compute the spring force vector between u and v + local attr_force = attractive_force(delta:norm()) + local force = delta:normalized():timesScalar(attr_force) + + -- move the node v accordingly + d = d:plus(force) + end + + -- really move the node now + -- TODO note how all nodes are moved by the same amount (step_length) + -- while Walshaw multiplies the normalized force with min(step_length, + -- d:norm()). could that improve this algorithm even further? + v.pos = v.pos:plus(d:normalized():timesScalar(step_length)) + + -- TODO Hu doesn't mention this but the energy of a particle is + -- typically considered as the product of its mass and the square of + -- its forces. This means we should probably take the weight of + -- the node v into the equation, doesn't it? + -- + -- update the energy function + energy = energy + math.pow(d:norm(), 2) + -- vector for the displacement of v + local d = Vector.new(2) + + -- compute repulsive forces + if self.approximate_repulsive_forces then + -- determine the cells that have a repulsive influence on v + local cells = quadtree:findInteractionCells(v, barnes_hut_criterion) + + -- compute the repulsive force between these cells and v + for _,cell in ipairs(cells) do + -- check if the cell is a leaf + if #cell.subcells == 0 then + -- compute the forces between the node and all particles in the cell + for _,particle in ipairs(cell.particles) do + local real_particles = lib.copy(particle.subparticles) + table.insert(real_particles, particle) + + for _,real_particle in ipairs(real_particles) do + local delta = real_particle.pos:minus(v.pos) + + -- enforce a small virtual distance if the node and the cell's + -- center of mass are located at (almost) the same position + if delta:norm() < 0.1 then + delta:update(function (n, value) return 0.1 + lib.random() * 0.1 end) + end + + -- compute the repulsive force vector + local repulsive_force = approximated_repulsive_force(delta:norm(), real_particle.mass) + local force = delta:normalized():timesScalar(repulsive_force) + + -- move the node v accordingly + d = d:plus(force) + end + end + else + -- compute the distance between the node and the cell's center of mass + local delta = cell.center_of_mass:minus(v.pos) + + -- enforce a small virtual distance if the node and the cell's + -- center of mass are located at (almost) the same position + if delta:norm() < 0.1 then + delta:update(function (n, value) return 0.1 + lib.random() * 0.1 end) + end + + -- compute the repulsive force vector + local repulsive_force = approximated_repulsive_force(delta:norm(), cell.mass) + local force = delta:normalized():timesScalar(repulsive_force) + + -- move the node v accordingly + d = d:plus(force) + end + end + else + for _,u in ipairs(graph.nodes) do + if v ~= u then + -- compute the distance between u and v + local delta = u.pos:minus(v.pos) + + -- enforce a small virtual distance if the nodes are + -- located at (almost) the same position + if delta:norm() < 0.1 then + delta:update(function (n, value) return 0.1 + lib.random() * 0.1 end) + end + + -- compute the repulsive force vector + local repulsive_force = accurate_repulsive_force(delta:norm(), u.weight) + local force = delta:normalized():timesScalar(repulsive_force) + + -- move the node v accordingly + d = d:plus(force) + end + end + end + + -- compute attractive forces between v and its neighbours + for _,edge in ipairs(v.edges) do + local u = edge:getNeighbour(v) + + -- compute the distance between u and v + local delta = u.pos:minus(v.pos) + + -- enforce a small virtual distance if the nodes are + -- located at (almost) the same position + if delta:norm() < 0.1 then + delta:update(function (n, value) return 0.1 + lib.random() * 0.1 end) + end + + -- compute the spring force vector between u and v + local attr_force = attractive_force(delta:norm()) + local force = delta:normalized():timesScalar(attr_force) + + -- move the node v accordingly + d = d:plus(force) + end + + -- really move the node now + -- TODO note how all nodes are moved by the same amount (step_length) + -- while Walshaw multiplies the normalized force with min(step_length, + -- d:norm()). could that improve this algorithm even further? + v.pos = v.pos:plus(d:normalized():timesScalar(step_length)) + + -- TODO Hu doesn't mention this but the energy of a particle is + -- typically considered as the product of its mass and the square of + -- its forces. This means we should probably take the weight of + -- the node v into the equation, doesn't it? + -- + -- update the energy function + energy = energy + math.pow(d:norm(), 2) + end + end + + -- update the step length and progress counter + step_length, progress = step_update_func(step_length, self.cooling_factor, energy, old_energy, progress) + + -- compute the maximum node movement in this iteration + local max_movement = 0 + for _,x in ipairs(graph.nodes) do + local delta = x.pos:minus(old_positions[x]) + max_movement = math.max(delta:norm(), max_movement) + end + + -- the algorithm will converge if the maximum movement is below a + -- threshold depending on the spring length and the convergence + -- tolerance + if max_movement < spring_length * self.convergence_tolerance then + converged = true + end + + -- increment the iteration counter + iteration = iteration + 1 + end +end + + + +-- Fixes nodes at their specified positions. +-- +function SpringElectricalHu2006:fixateNodes(graph) + local number_of_fixed_nodes = 0 + + for _,node in ipairs(graph.nodes) do + -- read the 'desired at' option of the node + local coordinate = node:getOption('desired at') + + if coordinate then + -- apply the coordinate + node.pos.x = coordinate.x + node.pos.y = coordinate.y + + -- mark the node as fixed + node.fixed = true + + number_of_fixed_nodes = number_of_fixed_nodes + 1 + end + end + if number_of_fixed_nodes > 1 then + self.growth_direction = "fixed" -- do not grow, orientation is now fixed + end +end + + + +function SpringElectricalHu2006:buildQuadtree(graph) + -- compute the minimum x and y coordinates of all nodes + local min_pos = graph.nodes[1].pos + for _,node in ipairs(graph.nodes) do + min_pos = Vector.new(2, function (n) return math.min(min_pos[n], node.pos[n]) end) + end + + -- compute maximum x and y coordinates of all nodes + local max_pos = graph.nodes[1].pos + for _,node in ipairs(graph.nodes) do + max_pos = Vector.new(2, function (n) return math.max(max_pos[n], node.pos[n]) end) + end + + -- make sure the maximum position is at least a tiny bit + -- larger than the minimum position + if min_pos:equals(max_pos) then + max_pos = max_pos:plus(Vector.new(2, function (n) + return 0.1 + lib.random() * 0.1 + end)) + end + + -- make sure to make the quadtree area slightly larger than required + -- in theory; for some reason Lua will otherwise think that nodes with + -- min/max x/y coordinates are outside the box... weird? yes. + min_pos = min_pos:minus({1,1}) + max_pos = max_pos:plus({1,1}) + + -- create the quadtree + quadtree = QuadTree.new(min_pos.x, min_pos.y, + max_pos.x - min_pos.x, + max_pos.y - min_pos.y) + + -- insert nodes into the quadtree + for _,node in ipairs(graph.nodes) do + local particle = QuadTree.Particle.new(node.pos, node.weight) + particle.node = node + quadtree:insert(particle) + end + + return quadtree +end + + + +function SpringElectricalHu2006.conservative_step_update(step, cooling_factor) + return cooling_factor * step, nil +end + + + +function SpringElectricalHu2006.adaptive_step_update(step, cooling_factor, energy, old_energy, progress) + if energy < old_energy then + progress = progress + 1 + if progress >= 5 then + progress = 0 + step = step / cooling_factor + end + else + progress = 0 + step = cooling_factor * step + end + return step, progress +end + + +-- done + +return SpringElectricalHu2006 diff --git a/graphics/pgf/base/tex/generic/graphdrawing/lua/pgf/gd/force/SpringElectricalLayouts.lua b/graphics/pgf/base/tex/generic/graphdrawing/lua/pgf/gd/force/SpringElectricalLayouts.lua new file mode 100644 index 0000000000..6ab74fc367 --- /dev/null +++ b/graphics/pgf/base/tex/generic/graphdrawing/lua/pgf/gd/force/SpringElectricalLayouts.lua @@ -0,0 +1,54 @@ +-- Copyright 2012 by Till Tantau +-- +-- This file may be distributed an/or modified +-- +-- 1. under the LaTeX Project Public License and/or +-- 2. under the GNU Public License +-- +-- See the file doc/generic/pgf/licenses/LICENSE for more information + +-- @release $Header$ + + +-- Imports +local declare = require("pgf.gd.interface.InterfaceToAlgorithms").declare + + +--- +-- @section subsection {Spring Electrical Layouts} +-- +-- @end + + + +--- + +declare { + key = "spring electrical layout", + use = { + { key = "spring electrical Hu 2006 layout" }, + { key = "spring constant", value = "0.2" } + }, + + summary = [[" + This key selects Hu's 2006 spring electrical layout with + appropriate settings for some parameters. + "]] +} + + +--- + +declare { + key = "spring electrical layout'", + use = { + { key = "spring electrical Walshaw 2000 layout" }, + { key = "spring constant", value = "0.01" }, + { key = "convergence tolerance", value = "0.001" }, + }, + + summary = [[" + This key selects Walshaw's 2000 spring electrical layout with + appropriate settings for some parameters. + "]] +} diff --git a/graphics/pgf/base/tex/generic/graphdrawing/lua/pgf/gd/force/SpringElectricalWalshaw2000.lua b/graphics/pgf/base/tex/generic/graphdrawing/lua/pgf/gd/force/SpringElectricalWalshaw2000.lua new file mode 100644 index 0000000000..5f7978da4e --- /dev/null +++ b/graphics/pgf/base/tex/generic/graphdrawing/lua/pgf/gd/force/SpringElectricalWalshaw2000.lua @@ -0,0 +1,520 @@ +-- Copyright 2011 by Jannis Pohlmann +-- Copyright 2012 by Till Tantau +-- +-- This file may be distributed and/or modified +-- +-- 1. under the LaTeX Project Public License and/or +-- 2. under the GNU Public License +-- +-- See the file doc/generic/pgf/licenses/LICENSE for more information + +-- @release $Header$ + + + + +local SpringElectricalWalshaw2000 = {} + +-- Imports +local declare = require("pgf.gd.interface.InterfaceToAlgorithms").declare + + + + +--- + +declare { + key = "spring electrical Walshaw 2000 layout", + algorithm = SpringElectricalWalshaw2000, + + preconditions = { + connected = true, + loop_free = true, + simple = true, + }, + + old_graph_model = true, + + summary = [[" + Implementation of a spring electrical graph drawing algorithm based on + a paper by Walshaw. + "]], + documentation = [[" + \begin{itemize} + \item + C. Walshaw. + \newblock A multilevel algorithm for force-directed graph drawing. + \newblock In J. Marks, editor, \emph{Graph Drawing}, Lecture Notes in + Computer Science, 1984:31--55, 2001. + \end{itemize} + + The following modifications compared to the original algorithm were applied: + % + \begin{itemize} + \item An iteration limit was added. + \item The natural spring length for all coarse graphs is computed based + on the formula presented by Walshaw, so that the natural spring + length of the original graph (coarse graph 0) is the same as + the value requested by the user. + \item Users can define custom node and edge weights. + \item Coarsening stops when $|V(G_i+1)|/|V(G_i)| < p$ where $p = 0.75$. + \item Coarsening stops when the maximal matching is empty. + \item The runtime of the algorithm is improved by use of a quadtree + data structure like Hu does in his algorithm. + \item A limiting the number of levels of the quadtree is not implemented. + \end{itemize} + "]] +} + +-- TODO Implement the following keys (or whatever seems appropriate +-- and doable for this algorithm): +-- - /tikz/desired at +-- - /tikz/influence cutoff distance +-- - /tikz/spring stiffness (could this be the equivalent to the electric +-- charge of nodes? +-- - /tikz/natural spring dimension per edge +-- +-- TODO Implement the following features: +-- - clustering of nodes using color classes +-- - different cluster layouts (vertical line, horizontal line, +-- normal cluster, internally fixed subgraph) + + + +local Vector = require "pgf.gd.deprecated.Vector" + +local QuadTree = require "pgf.gd.force.QuadTree" +local CoarseGraph = require "pgf.gd.force.CoarseGraph" + + +local lib = require "pgf.gd.lib" + + +function SpringElectricalWalshaw2000:run() + + -- Setup parameters + local options = self.digraph.options + + self.iterations = options['iterations'] + self.cooling_factor = options['cooling factor'] + self.initial_step_length = options['initial step length'] + self.convergence_tolerance = options['convergence tolerance'] + + self.natural_spring_length = options['node distance'] + self.spring_constant = options['spring constant'] + + self.approximate_repulsive_forces = options['approximate remote forces'] + self.repulsive_force_order = options['electric force order'] + + self.coarsen = options['coarsen'] + self.downsize_ratio = options['downsize ratio'] + self.minimum_graph_size = options['minimum coarsening size'] + + -- Adjust types + self.downsize_ratio = math.max(0, math.min(1, self.downsize_ratio)) + self.graph_size = #self.graph.nodes + self.graph_density = (2 * #self.graph.edges) / (#self.graph.nodes * (#self.graph.nodes - 1)) + + -- validate input parameters + assert(self.iterations >= 0, 'iterations (value: ' .. self.iterations .. ') need to be greater than 0') + assert(self.cooling_factor >= 0 and self.cooling_factor <= 1, 'the cooling factor (value: ' .. self.cooling_factor .. ') needs to be between 0 and 1') + assert(self.initial_step_length >= 0, 'the initial step length (value: ' .. self.initial_step_length .. ') needs to be greater than or equal to 0') + assert(self.convergence_tolerance >= 0, 'the convergence tolerance (value: ' .. self.convergence_tolerance .. ') needs to be greater than or equal to 0') + assert(self.natural_spring_length >= 0, 'the natural spring dimension (value: ' .. self.natural_spring_length .. ') needs to be greater than or equal to 0') + assert(self.spring_constant >= 0, 'the spring constant (value: ' .. self.spring_constant .. ') needs to be greater or equal to 0') + assert(self.downsize_ratio >= 0 and self.downsize_ratio <= 1, 'the downsize ratio (value: ' .. self.downsize_ratio .. ') needs to be between 0 and 1') + assert(self.minimum_graph_size >= 2, 'the minimum coarsening size of coarse graphs (value: ' .. self.minimum_graph_size .. ') needs to be greater than or equal to 2') + + -- initialize node weights + for _,node in ipairs(self.graph.nodes) do + if node:getOption('electric charge') ~= nil then + node.weight = node:getOption('electric charge') + else + node.weight = 1 + end + + -- a node is charged if its weight derives from the default setting + -- of 1 (where it has no influence on the forces) + node.charged = node.weight ~= 1 + end + + -- initialize edge weights + for _,edge in ipairs(self.graph.edges) do + edge.weight = 1 + end + + + -- initialize the coarse graph data structure. note that the algorithm + -- is the same regardless whether coarsening is used, except that the + -- number of coarsening steps without coarsening is 0 + local coarse_graph = CoarseGraph.new(self.graph) + + -- check if the multilevel approach should be used + if self.coarsen then + -- coarsen the graph repeatedly until only minimum_graph_size nodes + -- are left or until the size of the coarse graph was not reduced by + -- at least the downsize ratio configured by the user + while coarse_graph:getSize() > self.minimum_graph_size + and coarse_graph:getRatio() < (1 - self.downsize_ratio) + do + coarse_graph:coarsen() + end + end + + -- compute the natural spring length for the coarsest graph in a way + -- that will result in the desired natural spring length in the + -- original graph + local spring_length = self.natural_spring_length / math.pow(math.sqrt(4/7), coarse_graph:getLevel()) + + if self.coarsen then + -- generate a random initial layout for the coarsest graph + self:computeInitialLayout(coarse_graph.graph, spring_length) + + -- undo coarsening step by step, applying the force-based sub-algorithm + -- to every intermediate coarse graph as well as the original graph + while coarse_graph:getLevel() > 0 do + -- interpolate the previous coarse graph + coarse_graph:interpolate() + + -- update the natural spring length so that, for the original graph, + -- it equals the natural spring dimension configured by the user + spring_length = spring_length * math.sqrt(4/7) + + -- apply the force-based algorithm to improve the layout + self:computeForceLayout(coarse_graph.graph, spring_length) + end + else + -- generate a random initial layout for the coarsest graph + self:computeInitialLayout(coarse_graph.graph, spring_length) + + -- apply the force-based algorithm to improve the layout + self:computeForceLayout(coarse_graph.graph, spring_length) + end +end + + + +function SpringElectricalWalshaw2000:computeInitialLayout(graph, spring_length) + -- TODO how can supernodes and fixed nodes go hand in hand? + -- maybe fix the supernode if at least one of its subnodes is + -- fixated? + + -- fixate all nodes that have a 'desired at' option. this will set the + -- node.fixed member to true and also set node.pos.x and node.pos.y + self:fixateNodes(graph) + + if #graph.nodes == 2 then + if not (graph.nodes[1].fixed and graph.nodes[2].fixed) then + local fixed_index = graph.nodes[2].fixed and 2 or 1 + local loose_index = graph.nodes[2].fixed and 1 or 2 + + if not graph.nodes[1].fixed and not graph.nodes[2].fixed then + -- both nodes can be moved, so we assume node 1 is fixed at (0,0) + graph.nodes[1].pos.x = 0 + graph.nodes[1].pos.y = 0 + end + + -- position the loose node relative to the fixed node, with + -- the displacement (random direction) matching the spring length + local direction = Vector.new{x = lib.random(1, 2), y = lib.random(1, 2)} + local distance = 3 * spring_length * self.graph_density * math.sqrt(self.graph_size) / 2 + local displacement = direction:normalized():timesScalar(distance) + + graph.nodes[loose_index].pos = graph.nodes[fixed_index].pos:plus(displacement) + else + -- both nodes are fixed, initial layout may be far from optimal + end + else + -- function to filter out fixed nodes + local function nodeNotFixed(node) return not node.fixed end + + -- use the random positioning technique + local function positioning_func(n) + local radius = 3 * spring_length * self.graph_density * math.sqrt(self.graph_size) / 2 + return lib.random(-radius, radius) + end + + -- compute initial layout based on the random positioning technique + for _,node in ipairs(graph.nodes) do + if not node.fixed then + node.pos.x = positioning_func(1) + node.pos.y = positioning_func(2) + end + end + end +end + + + +function SpringElectricalWalshaw2000:computeForceLayout(graph, spring_length) + -- global (=repulsive) force function + local function accurate_repulsive_force(distance, weight) + return - self.spring_constant * weight * math.pow(spring_length, self.repulsive_force_order + 1) / math.pow(distance, self.repulsive_force_order) + end + + -- global (=repulsive, approximated) force function + local function approximated_repulsive_force(distance, mass) + return - mass * self.spring_constant * math.pow(spring_length, self.repulsive_force_order + 1) / math.pow(distance, self.repulsive_force_order) + end + + -- local (spring) force function + local function attractive_force(distance, d, weight, charged, repulsive_force) + -- for charged nodes, never subtract the repulsive force; we want ALL other + -- nodes to be attracted more / repulsed less (not just non-adjacent ones), + -- depending on the charge of course + if charged then + return (distance - spring_length) / d - accurate_repulsive_force(distance, weight) + else + return (distance - spring_length) / d - (repulsive_force or 0) + end + end + + -- define the Barnes-Hut opening criterion + function barnes_hut_criterion(cell, particle) + local distance = particle.pos:minus(cell.center_of_mass):norm() + return cell.width / distance <= 1.2 + end + + -- fixate all nodes that have a 'desired at' option. this will set the + -- node.fixed member to true and also set node.pos.x and node.pos.y + self:fixateNodes(graph) + + -- adjust the initial step length automatically if desired by the user + local step_length = self.initial_step_length == 0 and spring_length or self.initial_step_length + + -- convergence criteria + local converged = false + local i = 0 + + while not converged and i < self.iterations do + + -- assume that we are converging + converged = true + i = i + 1 + + -- build the quadtree for approximating repulsive forces, if desired + local quadtree = nil + if self.approximate_repulsive_forces then + quadtree = self:buildQuadtree(graph) + end + + local function nodeNotFixed(node) return not node.fixed end + + -- iterate over all nodes + for _,v in ipairs(graph.nodes) do + if not v.fixed then + -- vector for the displacement of v + local d = Vector.new(2) + + -- repulsive force induced by other nodes + local repulsive_forces = {} + + -- compute repulsive forces + if self.approximate_repulsive_forces then + -- determine the cells that have an repulsive influence on v + local cells = quadtree:findInteractionCells(v, barnes_hut_criterion) + + -- compute the repulsive force between these cells and v + for _,cell in ipairs(cells) do + -- check if the cell is a leaf + if #cell.subcells == 0 then + -- compute the forces between the node and all particles in the cell + for _,particle in ipairs(cell.particles) do + -- build a table that contains the particle plus all its subparticles + -- (particles at the same position) + local real_particles = lib.copy(particle.subparticles) + table.insert(real_particles, particle) + + for _,real_particle in ipairs(real_particles) do + local delta = real_particle.pos:minus(v.pos) + + -- enforce a small virtual distance if the node and the cell's + -- center of mass are located at (almost) the same position + if delta:norm() < 0.1 then + delta:update(function (n, value) return 0.1 + lib.random() * 0.1 end) + end + + -- compute the repulsive force vector + local repulsive_force = approximated_repulsive_force(delta:norm(), real_particle.mass) + local force = delta:normalized():timesScalar(repulsive_force) + + -- remember the repulsive force for the particle so that we can + -- subtract it later when computing the attractive forces with + -- adjacent nodes + repulsive_forces[real_particle.node] = repulsive_force + + -- move the node v accordingly + d = d:plus(force) + end + end + else + -- compute the distance between the node and the cell's center of mass + local delta = cell.center_of_mass:minus(v.pos) + + -- enforce a small virtual distance if the node and the cell's + -- center of mass are located at (almost) the same position + if delta:norm() < 0.1 then + delta:update(function (n, value) return 0.1 + lib.random() * 0.1 end) + end + + -- compute the repulsive force vector + local repulsive_force = approximated_repulsive_force(delta:norm(), cell.mass) + local force = delta:normalized():timesScalar(repulsive_force) + + -- TODO for each neighbor of v, check if it is in this cell. + -- if this is the case, compute the quadtree force for the mass + -- 'node.weight / cell.mass' and remember this as the repulsive + -- force of the neighbor; (it is not necessarily at + -- the center of mass of the cell, so the result is only an + -- approximation of the real repulsive force generated by the + -- neighbor) + + -- move the node v accordingly + d = d:plus(force) + end + end + else + for _,u in ipairs(graph.nodes) do + if u.name ~= v.name then + -- compute the distance between u and v + local delta = u.pos:minus(v.pos) + + -- enforce a small virtual distance if the nodes are + -- located at (almost) the same position + if delta:norm() < 0.1 then + delta:update(function (n, value) return 0.1 + lib.random() * 0.1 end) + end + + -- compute the repulsive force vector + local repulsive_force = accurate_repulsive_force(delta:norm(), u.weight) + local force = delta:normalized():timesScalar(repulsive_force) + + -- remember the repulsive force so we can later subtract them + -- when computing the attractive forces + repulsive_forces[u] = repulsive_force + + -- move the node v accordingly + d = d:plus(force) + end + end + end + + -- compute attractive forces between v and its neighbors + for _,edge in ipairs(v.edges) do + local u = edge:getNeighbour(v) + + -- compute the distance between u and v + local delta = u.pos:minus(v.pos) + + -- enforce a small virtual distance if the nodes are + -- located at (almost) the same position + if delta:norm() < 0.1 then + delta:update(function (n, value) return 0.1 + lib.random() * 0.1 end) + end + + -- compute the spring force between them + local attr_force = attractive_force(delta:norm(), #v.edges, u.weight, u.charged, repulsive_forces[u]) + local force = delta:normalized():timesScalar(attr_force) + + -- move the node v accordingly + d = d:plus(force) + end + + -- remember the previous position of v + old_position = v.pos:copy() + + if d:norm() > 0 then + -- reposition v according to the force vector and the current temperature + v.pos = v.pos:plus(d:normalized():timesScalar(math.min(step_length, d:norm()))) + end + + -- we need to improve the system energy as long as any of + -- the node movements is large enough to assume we're far + -- away from the minimum system energy + if v.pos:minus(old_position):norm() > spring_length * self.convergence_tolerance then + converged = false + end + end + end + + -- update the step length using the conservative cooling scheme + step_length = self.cooling_factor * step_length + end +end + + + +-- Fixes nodes at their specified positions. +-- +function SpringElectricalWalshaw2000:fixateNodes(graph) + local number_of_fixed_nodes = 0 + + for _,node in ipairs(graph.nodes) do + -- read the 'desired at' option of the node + local coordinate = node:getOption('desired at') + + if coordinate then + -- parse the coordinate + node.pos.x = coordinate.x + node.pos.y = coordinate.y + + -- mark the node as fixed + node.fixed = true + + number_of_fixed_nodes = number_of_fixed_nodes + 1 + end + end + if number_of_fixed_nodes > 1 then + self.growth_direction = "fixed" -- do not grow, orientation is now fixed + end +end + + + +function SpringElectricalWalshaw2000:buildQuadtree(graph) + -- compute the minimum x and y coordinates of all nodes + local min_pos = graph.nodes[1].pos + for _,node in ipairs(graph.nodes) do + min_pos = Vector.new(2, function (n) return math.min(min_pos[n], node.pos[n]) end) + end + + -- compute maximum x and y coordinates of all nodes + local max_pos = graph.nodes[1].pos + for _,node in ipairs(graph.nodes) do + max_pos = Vector.new(2, function (n) return math.max(max_pos[n], node.pos[n]) end) + end + + -- make sure the maximum position is at least a tiny bit + -- larger than the minimum position + if min_pos:equals(max_pos) then + max_pos = max_pos:plus(Vector.new(2, function (n) + return 0.1 + lib.random() * 0.1 + end)) + end + + -- make sure to make the quadtree area slightly larger than required + -- in theory; for some reason Lua will otherwise think that nodes with + -- min/max x/y coordinates are outside the box... weird? yes. + min_pos = min_pos:minusScalar(1) + max_pos = max_pos:plusScalar(1) + + -- create the quadtree + quadtree = QuadTree.new(min_pos.x, min_pos.y, + max_pos.x - min_pos.x, + max_pos.y - min_pos.y) + + -- insert nodes into the quadtree + for _,node in ipairs(graph.nodes) do + local particle = QuadTree.Particle.new(node.pos, node.weight) + particle.node = node + quadtree:insert(particle) + end + + return quadtree +end + + + +-- done + +return SpringElectricalWalshaw2000 diff --git a/graphics/pgf/base/tex/generic/graphdrawing/lua/pgf/gd/force/SpringHu2006.lua b/graphics/pgf/base/tex/generic/graphdrawing/lua/pgf/gd/force/SpringHu2006.lua new file mode 100644 index 0000000000..86b65abc83 --- /dev/null +++ b/graphics/pgf/base/tex/generic/graphdrawing/lua/pgf/gd/force/SpringHu2006.lua @@ -0,0 +1,386 @@ +-- Copyright 2011 by Jannis Pohlmann +-- Copyright 2012 by Till Tantau +-- +-- This file may be distributed and/or modified +-- +-- 1. under the LaTeX Project Public License and/or +-- 2. under the GNU Public License +-- +-- See the file doc/generic/pgf/licenses/LICENSE for more information + +-- @release $Header$ + + + +local SpringHu2006 = {} + +-- Imports +local declare = require("pgf.gd.interface.InterfaceToAlgorithms").declare + + + + +--- + +declare { + key = "spring Hu 2006 layout", + algorithm = SpringHu2006, + + preconditions = { + connected = true, + loop_free = true, + simple = true, + }, + + old_graph_model = true, + + summary = [[" + Implementation of a spring graph drawing algorithm based on + a paper by Hu. + "]], + documentation = [[" + \begin{itemize} + \item + Y. Hu. + \newblock Efficient, high-quality force-directed graph drawing. + \newblock \emph{The Mathematica Journal}, 2006. + \end{itemize} + + There are some modifications compared to the original algorithm, + see the Diploma thesis of Pohlmann for details. + "]] +} + + +-- Imports + +local PathLengths = require "pgf.gd.lib.PathLengths" +local Vector = require "pgf.gd.deprecated.Vector" + +local CoarseGraph = require "pgf.gd.force.CoarseGraph" + +local lib = require("pgf.gd.lib") + + + + +function SpringHu2006:run() + + -- Setup some parameters + local options = self.digraph.options + + self.iterations = options['iterations'] + self.cooling_factor = options['cooling factor'] + self.initial_step_length = options['initial step length'] + self.convergence_tolerance = options['convergence tolerance'] + + self.natural_spring_length = options['node distance'] + + self.coarsen = options['coarsen'] + self.downsize_ratio = options['downsize ratio'] + self.minimum_graph_size = options['minimum coarsening size'] + + + -- Setup + + self.downsize_ratio = math.max(0, math.min(1, tonumber(self.downsize_ratio))) + + self.graph_size = #self.graph.nodes + self.graph_density = (2 * #self.graph.edges) / (#self.graph.nodes * (#self.graph.nodes - 1)) + + -- validate input parameters + assert(self.iterations >= 0, 'iterations (value: ' .. self.iterations .. ') need to be greater than 0') + assert(self.cooling_factor >= 0 and self.cooling_factor <= 1, 'the cooling factor (value: ' .. self.cooling_factor .. ') needs to be between 0 and 1') + assert(self.initial_step_length >= 0, 'the initial step length (value: ' .. self.initial_step_length .. ') needs to be greater than or equal to 0') + assert(self.convergence_tolerance >= 0, 'the convergence tolerance (value: ' .. self.convergence_tolerance .. ') needs to be greater than or equal to 0') + assert(self.natural_spring_length >= 0, 'the natural spring dimension (value: ' .. self.natural_spring_length .. ') needs to be greater than or equal to 0') + assert(self.downsize_ratio >= 0 and self.downsize_ratio <= 1, 'the downsize ratio (value: ' .. self.downsize_ratio .. ') needs to be between 0 and 1') + assert(self.minimum_graph_size >= 2, 'the minimum coarsening size of coarse graphs (value: ' .. self.minimum_graph_size .. ') needs to be greater than or equal to 2') + + -- initialize node weights + for _,node in ipairs(self.graph.nodes) do + node.weight = 1 + end + + -- initialize edge weights + for _,edge in ipairs(self.graph.edges) do + edge.weight = 1 + end + + + -- initialize the coarse graph data structure. note that the algorithm + -- is the same regardless whether coarsening is used, except that the + -- number of coarsening steps without coarsening is 0 + local coarse_graph = CoarseGraph.new(self.graph) + + -- check if the multilevel approach should be used + if self.coarsen then + -- coarsen the graph repeatedly until only minimum_graph_size nodes + -- are left or until the size of the coarse graph was not reduced by + -- at least the downsize ratio configured by the user + while coarse_graph:getSize() > self.minimum_graph_size + and coarse_graph:getRatio() <= (1 - self.downsize_ratio) + do + coarse_graph:coarsen() + end + end + + if self.coarsen then + -- use the natural spring length as the initial natural spring length + local spring_length = self.natural_spring_length + + -- compute a random initial layout for the coarsest graph + self:computeInitialLayout(coarse_graph.graph, spring_length) + + -- set the spring length to the average edge length of the initial layout + spring_length = 0 + for _,edge in ipairs(coarse_graph.graph.edges) do + spring_length = spring_length + edge.nodes[1].pos:minus(edge.nodes[2].pos):norm() + end + spring_length = spring_length / #coarse_graph.graph.edges + + -- additionally improve the layout with the force-based algorithm + -- if there are more than two nodes in the coarsest graph + if coarse_graph:getSize() > 2 then + self:computeForceLayout(coarse_graph.graph, spring_length, SpringHu2006.adaptive_step_update) + end + + -- undo coarsening step by step, applying the force-based sub-algorithm + -- to every intermediate coarse graph as well as the original graph + while coarse_graph:getLevel() > 0 do + -- compute the diameter of the parent coarse graph + local parent_diameter = PathLengths.pseudoDiameter(coarse_graph.graph) + + -- interpolate the previous coarse graph from its parent + coarse_graph:interpolate() + + -- compute the diameter of the current coarse graph + local current_diameter = PathLengths.pseudoDiameter(coarse_graph.graph) + + -- scale node positions by the quotient of the pseudo diameters + for _,node in ipairs(coarse_graph.graph) do + node.pos:update(function (n, value) + return value * (current_diameter / parent_diameter) + end) + end + + -- compute forces in the graph + self:computeForceLayout(coarse_graph.graph, spring_length, SpringHu2006.conservative_step_update) + end + else + -- compute a random initial layout for the coarsest graph + self:computeInitialLayout(coarse_graph.graph, self.natural_spring_length) + + -- set the spring length to the average edge length of the initial layout + spring_length = 0 + for _,edge in ipairs(coarse_graph.graph.edges) do + spring_length = spring_length + edge.nodes[1].pos:minus(edge.nodes[2].pos):norm() + end + spring_length = spring_length / #coarse_graph.graph.edges + + -- improve the layout with the force-based algorithm + self:computeForceLayout(coarse_graph.graph, spring_length, SpringHu2006.adaptive_step_update) + end + + local avg_spring_length = 0 + for _,edge in ipairs(self.graph.edges) do + avg_spring_length = avg_spring_length + edge.nodes[1].pos:minus(edge.nodes[2].pos):norm() + end + avg_spring_length = avg_spring_length / #self.graph.edges +end + + + +function SpringHu2006:computeInitialLayout(graph, spring_length) + -- TODO how can supernodes and fixed nodes go hand in hand? + -- maybe fix the supernode if at least one of its subnodes is + -- fixated? + + -- fixate all nodes that have a 'desired at' option. this will set the + -- node.fixed member to true and also set node.pos.x and node.pos.y + self:fixateNodes(graph) + + if #graph.nodes == 2 then + if not (graph.nodes[1].fixed and graph.nodes[2].fixed) then + local fixed_index = graph.nodes[2].fixed and 2 or 1 + local loose_index = graph.nodes[2].fixed and 1 or 2 + + if not graph.nodes[1].fixed and not graph.nodes[2].fixed then + -- both nodes can be moved, so we assume node 1 is fixed at (0,0) + graph.nodes[1].pos.x = 0 + graph.nodes[1].pos.y = 0 + end + + -- position the loose node relative to the fixed node, with + -- the displacement (random direction) matching the spring length + local direction = Vector.new{x = lib.random(1, spring_length), y = lib.random(1, spring_length)} + local distance = 1.8 * spring_length * self.graph_density * math.sqrt(self.graph_size) / 2 + local displacement = direction:normalized():timesScalar(distance) + + graph.nodes[loose_index].pos = graph.nodes[fixed_index].pos:plus(displacement) + else + -- both nodes are fixed, initial layout may be far from optimal + end + else + -- use a random positioning technique + local function positioning_func(n) + local radius = 2 * spring_length * self.graph_density * math.sqrt(self.graph_size) / 2 + return lib.random(-radius, radius) + end + + -- compute initial layout based on the random positioning technique + for _,node in ipairs(graph.nodes) do + if not node.fixed then + node.pos.x = positioning_func(1) + node.pos.y = positioning_func(2) + end + end + end +end + + + +function SpringHu2006:computeForceLayout(graph, spring_length, step_update_func) + -- global (=repulsive) force function + function repulsive_force(distance, graph_distance, weight) + --return (1/4) * (1/math.pow(graph_distance, 2)) * (distance - (spring_length * graph_distance)) + return (distance - (spring_length * graph_distance)) + end + + -- fixate all nodes that have a 'desired at' option. this will set the + -- node.fixed member to true and also set node.pos.x and node.pos.y + self:fixateNodes(graph) + + -- adjust the initial step length automatically if desired by the user + local step_length = self.initial_step_length == 0 and spring_length or self.initial_step_length + + -- convergence criteria etc. + local converged = false + local energy = math.huge + local iteration = 0 + local progress = 0 + + -- compute graph distance between all pairs of nodes + local distances = PathLengths.floydWarshall(graph) + + while not converged and iteration < self.iterations do + -- remember old node positions + local old_positions = lib.map(graph.nodes, function (node) return node.pos:copy(), node end) + + -- remember the old system energy and reset it for the current iteration + local old_energy = energy + energy = 0 + + for _,v in ipairs(graph.nodes) do + if not v.fixed then + -- vector for the displacement of v + local d = Vector.new(2) + + for _,u in ipairs(graph.nodes) do + if v ~= u then + -- compute the distance between u and v + local delta = u.pos:minus(v.pos) + + -- enforce a small virtual distance if the nodes are + -- located at (almost) the same position + if delta:norm() < 0.1 then + delta:update(function (n, value) return 0.1 + lib.random() * 0.1 end) + end + + local graph_distance = (distances[u] and distances[u][v]) and distances[u][v] or #graph.nodes + 1 + + -- compute the repulsive force vector + local force = repulsive_force(delta:norm(), graph_distance, v.weight) + local force = delta:normalized():timesScalar(force) + + -- move the node v accordingly + d = d:plus(force) + end + end + + -- really move the node now + -- TODO note how all nodes are moved by the same amount (step_length) + -- while Walshaw multiplies the normalized force with min(step_length, + -- d:norm()). could that improve this algorithm even further? + v.pos = v.pos:plus(d:normalized():timesScalar(step_length)) + + -- update the energy function + energy = energy + math.pow(d:norm(), 2) + end + end + + -- update the step length and progress counter + step_length, progress = step_update_func(step_length, self.cooling_factor, energy, old_energy, progress) + + -- compute the maximum node movement in this iteration + local max_movement = 0 + for _,x in ipairs(graph.nodes) do + local delta = x.pos:minus(old_positions[x]) + max_movement = math.max(delta:norm(), max_movement) + end + + -- the algorithm will converge if the maximum movement is below a + -- threshold depending on the spring length and the convergence + -- tolerance + if max_movement < spring_length * self.convergence_tolerance then + converged = true + end + + -- increment the iteration counter + iteration = iteration + 1 + end +end + + + +-- Fixes nodes at their specified positions. +-- +function SpringHu2006:fixateNodes(graph) + local number_of_fixed_nodes = 0 + + for _,node in ipairs(graph.nodes) do + -- read the 'desired at' option of the node + local coordinate = node:getOption('desired at') + + if coordinate then + -- apply the coordinate + node.pos.x = coordinate.x + node.pos.y = coordinate.y + + -- mark the node as fixed + node.fixed = true + + number_of_fixed_nodes = number_of_fixed_nodes + 1 + end + end + if number_of_fixed_nodes > 1 then + self.growth_direction = "fixed" -- do not grow, orientation is now fixed + end +end + + + +function SpringHu2006.conservative_step_update(step, cooling_factor) + return cooling_factor * step, nil +end + + + +function SpringHu2006.adaptive_step_update(step, cooling_factor, energy, old_energy, progress) + if energy < old_energy then + progress = progress + 1 + if progress >= 5 then + progress = 0 + step = step / cooling_factor + end + else + progress = 0 + step = cooling_factor * step + end + return step, progress +end + + +-- done + +return SpringHu2006 diff --git a/graphics/pgf/base/tex/generic/graphdrawing/lua/pgf/gd/force/SpringLayouts.lua b/graphics/pgf/base/tex/generic/graphdrawing/lua/pgf/gd/force/SpringLayouts.lua new file mode 100644 index 0000000000..e3ac58d571 --- /dev/null +++ b/graphics/pgf/base/tex/generic/graphdrawing/lua/pgf/gd/force/SpringLayouts.lua @@ -0,0 +1,36 @@ +-- Copyright 2012 by Till Tantau +-- +-- This file may be distributed an/or modified +-- +-- 1. under the LaTeX Project Public License and/or +-- 2. under the GNU Public License +-- +-- See the file doc/generic/pgf/licenses/LICENSE for more information + +-- @release $Header$ + + +-- Imports +local declare = require("pgf.gd.interface.InterfaceToAlgorithms").declare + + +--- +-- @section subsection {Spring Layouts} +-- +-- @end + + + +--- + +declare { + key = "spring layout", + use = { + { key = "spring Hu 2006 layout" }, + }, + + summary = [[" + This key selects Hu's 2006 spring layout with appropriate settings + for some parameters. + "]] +} diff --git a/graphics/pgf/base/tex/generic/graphdrawing/lua/pgf/gd/force/jedi/algorithms/FruchtermanReingold.lua b/graphics/pgf/base/tex/generic/graphdrawing/lua/pgf/gd/force/jedi/algorithms/FruchtermanReingold.lua new file mode 100644 index 0000000000..2450bba2a2 --- /dev/null +++ b/graphics/pgf/base/tex/generic/graphdrawing/lua/pgf/gd/force/jedi/algorithms/FruchtermanReingold.lua @@ -0,0 +1,124 @@ +-- Copyright 2014 by Ida Bruhns +-- +-- This file may be distributed and/or modified +-- +-- 1. under the LaTeX Project Public License and/or +-- 2. under the GNU Public License +-- +-- See the file doc/generic/pgf/licenses/LICENSE for more information + +local SpringElectricNoCoarsenClass = {} + +-- Imports +local declare = require "pgf.gd.interface.InterfaceToAlgorithms".declare +local ForceController = require "pgf.gd.force.jedi.base.ForceController" +local ForceCanvasDistance = require "pgf.gd.force.jedi.forcetypes.ForceCanvasDistance" +local ForceGraphDistance = require "pgf.gd.force.jedi.forcetypes.ForceGraphDistance" +local Storage = require "pgf.gd.lib.Storage" + +--- +declare { + key = "spring electric no coarsen layout", + algorithm = SpringElectricNoCoarsenClass, + preconditions = { connected = true }, + postconditions = {fixed = true}, + + summary = [[ + This layout uses the algorithm proposed by Fruchterman and Reingold to draw graphs." + ]], + + documentation = [[ + The Fruchterman-Reingold algorithm is one if the oldest methods + for force-based graph drawing. It is described in: + % + \begin{itemize} + \item + Thomas M.~J.~ Fruchterman and Edward M.~ Reingold, + \newblock Graph Drawing by Force-directed Placement, + \newblock \emph{Software -- practice and experience,} + 21(1 1), 1129-1164, 1991. + \end{itemize} + % + Fruchterman and Reingold had to principles in graph drawing: + % + \begin{enumerate} + \item Vertices connected by an edge should be drawn close to another and + \item in general, vertices should not be drawn too close to each other. + \end{itemize} + % + The spring electric no coarsen layout uses spring forces as attractive + forces influencing vertex pairs connected by an edge and electric forces + as repulsive forces between all vertex pairs. The original algorithm + also contained a frame that stopped the vertices from drifting too far + apart, but this concept was not implemented. This algorithm will not be + affected by coarsening. This layout was implemented by using the Jedi + framework. + ]], + + example = + [[ + \tikz + \graph[spring electric no coarsen layout, speed = 0.35, node distance = 2.5cm, nodes={as=,circle, draw, inner sep=3pt,outer sep=0pt}, coarsen = true, maximum step = 1]{ + a -- {b, c, d, e, f, g, h, i, j}, + b -- {c, d, e, f, g, h, i, j}, + c -- {d, e, f, g, h, i, j}, + d -- {e, f, g, h, i, j}, + e -- {f, g, h, i, j}, + f -- {g, h, i, j}, + g -- {h, i, j}, + h -- {i, j}, + i -- j + }; + ]], + + example = + [[ + \graph[spring electric no coarsen layout, speed = 0.25, node distance = 0.25cm, horizontal = c to l, nodes={as=,circle, draw, inner sep=3pt,outer sep=0pt}, coarsen = false, maximum step = 1]{ + a -> b -> c -> {d1 -> e -> f -> g -> h -> i -> {j1 -> e, j2 -> l}, d2 -> l -> m}, m -> a + }; + ]] +} + + + + +-- Implementation starts here + +--define a local time function +local time_fun_1 +function time_fun_1 (t_total, t_now) + if t_now/t_total <= 0.5 then + return 0.5 + else + return 2 + end +end + +-- define storage table to add attributes if wanted +local fw_attributes = Storage.newTableStorage() + +function SpringElectricNoCoarsenClass:run() + -- add options to storage table + fw_attributes.options = self.ugraph.options + + --Generate new force class + local spring_electric_no_coarsen = ForceController.new(self.ugraph) + + spring_electric_no_coarsen:addForce{ + force_type = ForceCanvasDistance, + fun_u = function (data) return data.k*data.k/(data.d) end, + time_fun = time_fun_1, + epoch = {"after expand"} + } + spring_electric_no_coarsen:addForce{ + force_type = ForceGraphDistance, + fun_u = function (data) return -data.d*data.d/(data.k) end, + n = 1, + epoch = {"after expand"} + } + + -- run algorithm + spring_electric_no_coarsen:run() +end + +return SpringElectricNoCoarsenClass
\ No newline at end of file diff --git a/graphics/pgf/base/tex/generic/graphdrawing/lua/pgf/gd/force/jedi/algorithms/HuSpringElectricalFW.lua b/graphics/pgf/base/tex/generic/graphdrawing/lua/pgf/gd/force/jedi/algorithms/HuSpringElectricalFW.lua new file mode 100644 index 0000000000..57cd1547b6 --- /dev/null +++ b/graphics/pgf/base/tex/generic/graphdrawing/lua/pgf/gd/force/jedi/algorithms/HuSpringElectricalFW.lua @@ -0,0 +1,95 @@ +-- Copyright 2014 by Ida Bruhns +-- +-- This file may be distributed and/or modified +-- +-- 1. under the LaTeX Project Public License and/or +-- 2. under the GNU Public License +-- +-- See the file doc/generic/pgf/licenses/LICENSE for more information + +local HuClass = {} + +-- Imports +local declare = require "pgf.gd.interface.InterfaceToAlgorithms".declare +local ForceController = require "pgf.gd.force.jedi.base.ForceController" +local ForceCanvasDistance = require "pgf.gd.force.jedi.forcetypes.ForceCanvasDistance" +local ForceGraphDistance = require "pgf.gd.force.jedi.forcetypes.ForceGraphDistance" + +--- +declare { + key = "jedi spring electric layout", + algorithm = HuClass, + documentation_in = "documentation_hu_layout", + preconditions = { connected = true }, + postconditions = {fixed = true}, + + summary = "This layout uses the spring electric algorithm proposed by Hu to draw graphs.", + + documentation = [[ + The spring electric algorithm by Hu uses two kinds of forces and coarsening. + It is described in: + % + \begin{itemize} + \item + Yifan Hu, + \newblock Efficient, high quality force-directed graph drawing, + \newblock \emph{The Mathematica Journal,} + 10(1), 37--71, 2006. + \end{itemize} + % + This algorithm uses spring forces as attractive forces between vertices + connected by an edge and electric forces as repulsive forces between + all vertex pairs. Hu introduces coarsening, a procedure which repeatedly + merges vertices in order to obtain a smaller version of the graph, to + overcome local minima. He also uses the Barnes-Hut algorithm to enhance + the runtime of his algorithms. This algorithm is not used in this + implementation. This layout was implemented by using the Jedi framework. + ]], + + example = + [[ + \tikz + \graph[spring electric fw layout, speed = 0.35, node distance = 5cm, nodes={as=,circle, draw, inner sep=3pt,outer sep=0pt}, maximum displacement per step = 10]{ + a -- {b, c, d, e}, + b -- {c, d, e}, + c -- {d, e}, + d --e + }; + ]], + + example = + [[ + \tikz + \graph[spring electric fw layout, speed = 0.35, node distance = 1cm, horizontal = c to l, nodes={as=,circle, draw, inner sep=3pt,outer sep=0pt}, maximum displacement per step = 10]{ + a -> b -> c -> {d1 -> e -> f -> g -> h -> i -> {j1 -> e, j2 -> l}, d2 -> l -> m}, m -> a + }; + ]] +} + + + + +-- Implementation starts here: + +function HuClass:run() + -- Generate new force class + local hu = ForceController.new(self.ugraph) + + -- add all required forces + hu:addForce{ + force_type = ForceCanvasDistance, + fun_u = function (data) return (data.k*data.k)/data.d end, + epoch = {"during expand", "after expand"} + } + hu:addForce{ + force_type = ForceGraphDistance, + fun_u = function (data) return -(data.d*data.d)/data.k end, + n = 1, + epoch = {"during expand", "after expand"} + } + + -- run algorithm + hu:run() +end + +return HuClass
\ No newline at end of file diff --git a/graphics/pgf/base/tex/generic/graphdrawing/lua/pgf/gd/force/jedi/algorithms/SimpleSpring.lua b/graphics/pgf/base/tex/generic/graphdrawing/lua/pgf/gd/force/jedi/algorithms/SimpleSpring.lua new file mode 100644 index 0000000000..4dbae2b1f4 --- /dev/null +++ b/graphics/pgf/base/tex/generic/graphdrawing/lua/pgf/gd/force/jedi/algorithms/SimpleSpring.lua @@ -0,0 +1,74 @@ +-- Copyright 2014 by Ida Bruhns +-- +-- This file may be distributed and/or modified +-- +-- 1. under the LaTeX Project Public License and/or +-- 2. under the GNU Public License +-- +-- See the file doc/generic/pgf/licenses/LICENSE for more information + +local SimpleSpringClass = {} + +-- Imports +local declare = require "pgf.gd.interface.InterfaceToAlgorithms".declare +local ForceController = require 'pgf.gd.force.jedi.base.ForceController' +local ForceGraphDistance = require "pgf.gd.force.jedi.forcetypes.ForceGraphDistance" + +--- +declare { + key = "trivial spring layout", + algorithm = SimpleSpringClass, + documentation_in = "pgf.gd.doc.jedi.algorithms.SimpleSpringLayout", + preconditions = { connected = true }, + postconditions = {fixed = true}, + + summary = "This layout uses only spring forces to draw graphs.", + + documentation = [[ + The simple spring algorithm only uses one force kind: A spring force + that serves as both attractive and repulsive force. The edges are modeled as + springs and act according to Hoke's law: They have an ideal length and will + expand if they are contracted below this length, pushing the adjacent + vertices away from each other, and contract if it is stretched, pulling the + adjacent vertices towards each other. This ideal length is given by the + parameter |node distance|. There is no force repelling vertices that are not + connected to each other, which can lead to vertices being placed at the same + point. It is not a very powerful layout and will probably fail with large + graphs, especially if they have few edges. It can however be used to + demonstrate the effect of spring forces. This layout was implemented by using + the Jedi framework. + ]], + + example = [[ + \tikz + \graph[simple spring layout, node distance = 3cm, speed = 2, nodes={as=,circle, draw, inner sep=3pt,outer sep=0pt}, coarsen = true, maximum step = 1]{ + a -- {b, c, d, e}, + b -- {c, d, e}, + c -- {d, e}, + d --e + }; + ]] +} + + + + +-- Implementation starts here: + +function SimpleSpringClass:run() + --Generate new force class + simple_spring = ForceController.new(self.ugraph) + + --add all required forces + simple_spring:addForce{ + force_type = ForceGraphDistance, + fun_u = function (data) return data.k*(data.k-data.d) end, + n = 1, + epoch = {"after expand", "during expand"} + } + + -- run algorithm + simple_spring:run() +end + +return SimpleSpringClass
\ No newline at end of file diff --git a/graphics/pgf/base/tex/generic/graphdrawing/lua/pgf/gd/force/jedi/algorithms/SocialGravityCloseness.lua b/graphics/pgf/base/tex/generic/graphdrawing/lua/pgf/gd/force/jedi/algorithms/SocialGravityCloseness.lua new file mode 100644 index 0000000000..1c8a1bb8d9 --- /dev/null +++ b/graphics/pgf/base/tex/generic/graphdrawing/lua/pgf/gd/force/jedi/algorithms/SocialGravityCloseness.lua @@ -0,0 +1,129 @@ +-- Copyright 2014 by Ida Bruhns +-- +-- This file may be distributed and/or modified +-- +-- 1. under the LaTeX Project Public License and/or +-- 2. under the GNU Public License +-- +-- See the file doc/generic/pgf/licenses/LICENSE for more information + +local SocialClass = {} + +--Imports +local declare = require "pgf.gd.interface.InterfaceToAlgorithms".declare +local ForceController = require 'pgf.gd.force.jedi.base.ForceController' +local ForceCanvasDistance = require "pgf.gd.force.jedi.forcetypes.ForceCanvasDistance" +local ForceCanvasPosition = require "pgf.gd.force.jedi.forcetypes.ForceCanvasPosition" +local ForceGraphDistance = require "pgf.gd.force.jedi.forcetypes.ForceGraphDistance" +local PathLengthsFW = require "pgf.gd.force.jedi.base.PathLengthsFW" +local Storage = require "pgf.gd.lib.Storage" + +--- +declare { + key = "social closeness layout", + algorithm = SocialClass, + postconditions = {fixed = true}, + + summary = [[ + This layout uses the social gravity algorithm proposed by Bannister + with closeness mass to draw graphs. + ]], + + documentation = [[ + Bannister et all described a social gravity algorithm that can be + implemented with different kinds of gravity. + It is described in: + % + \begin{itemize} + \item Michael J.~ Bannister and David Eppstein and Michael T~. Goodrich + and Lowell Trott, + \newblock Force-Directed Graph Drawing Using Social Gravity and Scaling, + \newblock \emph{CoRR,} + abs/1209.0748, 2012. + \end{itemize} + % + This implementation uses the closeness mass to determine the gravity of each + vertex. There are three forces in this algorithm: A spring force as + attractive force between vertices connected by an edge, an electric force as + repulsive force between all vertex pairs, and a gravitational force pulling + all vertices closer to their midpoint. The gravitational force depends on + the social mass of a vertex, which can be determined in different ways. This + algorithm uses the closeness mass. The closeness of a vertex $u$ is the + reciprocal of the sum of the shortest path from $u$ to every other vertex + $v$. The gravitational force leads to more "important" vertices ending up + closer to the middle of the drawing, since the social mass of a vertex is + proportional to its importance. The social layouts work especially well on + unconnected graphs like forests. This layout was implemented by using the + Jedi framework. + ]], + + example = [[ + \tikz + \graph[social closeness layout, speed = 0.9, gravity = 0.2, node distance = 0.65cm, nodes={as=,circle, draw, inner sep=3pt,outer sep=0pt}, find equilibrium = true, maximum step = 5]{ + a -- a1 -- a2 -- a, + b -- b1 -- b2 -- b, + c -- c1 -- c2 -- c, + d -- d1 -- d2 -- d, + e -- e1 -- e2 -- e, + f -- f1 -- f2 -- f, + g -- g1 -- g2 -- g, + h -- h1 -- h2 -- h, + i -- i1 -- i2 -- i, + j -- j1 -- j2 -- j, + a -- b -- c -- d -- e -- f -- g -- h -- i -- j -- a + }; + ]], + + example = [[ + \tikz + \graph[social closeness layout, speed = 0.35, node distance = 0.7cm, maximum step = 5, nodes={as=,circle, draw, inner sep=3pt,outer sep=0pt}, radius = 1cm, gravity = 2]{ + a -- {a1 -- a2, a3}, + b -- {b1, b2 -- b3 -- b4 --{b5, b6}}, + c -- {c1--c2}, + d -- {d1, d2, d3 -- {d4, d5}, d6 --{d7, d8}} + }; + ]] +} + +local fw_attributes = Storage.newTableStorage() + +function SocialClass:run() + local dist = PathLengthsFW:breadthFirstSearch(self.ugraph) + local tmp + for vertex, n in pairs(dist) do + tmp = fw_attributes[vertex] + local sum = 0 + for i, w in pairs(n) do + sum = sum + w + end + sum = sum / # self.ugraph.vertices + tmp.mass = 1/sum + end + + fw_attributes.options = self.ugraph.options + + --Generate new force class + social_gravity = ForceController.new(self.ugraph, fw_attributes) + + --add all required forces + social_gravity:addForce{ + force_type = ForceCanvasDistance, + fun_u = function (data) return data.k/(data.d*data.d) end, + epoch = {"after expand", "during expand"} + } + social_gravity:addForce{ + force_type = ForceCanvasPosition, + fun_u = function (data) return data.attributes[data.u].mass*data.attributes.options.gravity end, + epoch = {"after expand", "during expand"} + } + social_gravity:addForce{ + force_type = ForceGraphDistance, + fun_u = function (data) return -data.d/(data.k*data.k) end, + n = 1, + epoch = {"after expand", "during expand"} + } + + social_gravity:run() +end + +return SocialClass diff --git a/graphics/pgf/base/tex/generic/graphdrawing/lua/pgf/gd/force/jedi/algorithms/SocialGravityDegree.lua b/graphics/pgf/base/tex/generic/graphdrawing/lua/pgf/gd/force/jedi/algorithms/SocialGravityDegree.lua new file mode 100644 index 0000000000..6408349107 --- /dev/null +++ b/graphics/pgf/base/tex/generic/graphdrawing/lua/pgf/gd/force/jedi/algorithms/SocialGravityDegree.lua @@ -0,0 +1,183 @@ +-- Copyright 2014 by Ida Bruhns +-- +-- This file may be distributed and/or modified +-- +-- 1. under the LaTeX Project Public License and/or +-- 2. under the GNU Public License +-- +-- See the file doc/generic/pgf/licenses/LICENSE for more information + + +local declare = require "pgf.gd.interface.InterfaceToAlgorithms".declare +local ForceController = require 'pgf.gd.force.jedi.base.ForceController' +local ForceCanvasDistance = require "pgf.gd.force.jedi.forcetypes.ForceCanvasDistance" +local ForceCanvasPosition = require "pgf.gd.force.jedi.forcetypes.ForceCanvasPosition" +local ForceGraphDistance = require "pgf.gd.force.jedi.forcetypes.ForceGraphDistance" +local Storage = require "pgf.gd.lib.Storage" + +local SocialClass = {} + +--- +declare { + key = "social degree layout", + algorithm = SocialClass, + postconditions = {fixed = true}, + + summary = [[ + This layout uses the social gravity algorithm proposed by Bannister + with closeness mass to draw graphs.]], + + documentation = [[ + Bannister et all described a social gravity algorithm that can be + implemented with different kinds of gravity. + It is described in: + % + \begin{itemize} + \item + Michael J.~ Bannister and David Eppstein and Michael T~. Goodrich and + Lowell Trott, + \newblock Force-Directed Graph Drawing Using Social Gravity and Scaling, + \newblock \emph{CoRR,} abs/1209.0748, 2012. + \end{itemize} + % + This implementation uses the degree mass to determine the gravity of each + vertex. There are three forces in this algorithm: A spring force as + attractive force between vertices connected by an edge, an electric force as + repulsive force between all vertex pairs, and a gravitational force pulling + all vertices closer to their midpoint. The gravitational force depends on + the social mass of a vertex, which can be determined in different ways. This + algorithm uses the degree of each vertex as its mass. The gravitational + force leads to more "important" vertices ending up closer to the middle of + the drawing, since the social mass of a vertex is proportional to its + importance. The social layouts work especially well on unconnected graphs + like forests. This layout was implemented by using the Jedi framework. + ]], + + example = + [[ + \tikz + \graph[social degree layout, speed = 0.9, gravity = 0.2, node distance = 0.65cm, nodes={as=,circle, draw, inner sep=3pt,outer sep=0pt}, find equilibrium = true, maximum step = 5]{ + a -- a1 -- a2 -- a, + b -- b1 -- b2 -- b, + c -- c1 -- c2 -- c, + d -- d1 -- d2 -- d, + e -- e1 -- e2 -- e, + f -- f1 -- f2 -- f, + g -- g1 -- g2 -- g, + h -- h1 -- h2 -- h, + i -- i1 -- i2 -- i, + j -- j1 -- j2 -- j, + a -- b -- c -- d -- e -- f -- g -- h -- i -- j -- a + }; + ]], + + example = + [[ + \tikz + \graph[social degree layout, speed = 0.35, node distance = 0.7cm, maximum step = 15, nodes={as=,circle, draw, inner sep=3pt,outer sep=0pt}, radius = 1cm, gravity = 0.2]{ + a -- {a1 -- a2, a3}, + b -- {b1, b2 -- b3 -- b4 --{b5, b6}}, + c -- {c1--c2}, + d -- {d1, d2, d3 -- {d4, d5}, d6 --{d7, d8}} + }; + ]] +} + +--- +declare { + key = "gravity", + type = "number", + initial = 0.2, + + summary = "The gravity key describes the magnitude of the gravitational force.", + + documentation = [[ + This parameter currently only affects the \lstinline{social degree layout} + and the \lstinline{social closeness layout}. The gravity key determines the + strength used to pull the vertices to the center of the canvas. + ]], + + example = + [[ + \tikz + \graph[social degree layout, iterations = 100, maximum time = 100, maximum step = 10]{ + a1[weight = 2] -- {a2, a3, a4, a5}, + b1 -- {b2 -- {b3, b4}, b5} + }; + ]], + + example = [[ + \tikz + \graph[social degree layout, iterations = 100, maximum time = 100, gravity = 0.5, maximum step = 10]{ + a1 -- {a2 [mass = 2], a3, a4, a5}, + b1 -- {b2 -- {b3, b4}, b5} + }; + ]] +} + + + + +-- Implementation starts here: + +-- define time functions +local time_fun_1, time_fun_2, time_fun_3 + +function time_fun_1 (t_total, t_now) + if t_now > 3*t_total/4 then + return t_now/t_total + end + return 0 +end + +function time_fun_3 (t_total, t_now) + if t_now >= t_total/2 then + return 2 + else + return 1 + end +end + +-- define table to store variables if needed +local fw_attributes = Storage.newTableStorage() + +function SocialClass:run() + --initialize masses + local tmp + for _, vertex in ipairs(self.ugraph.vertices) do + tmp = fw_attributes[vertex] + tmp.social_mass = #self.ugraph:incoming(vertex) + end + + -- add options to storage table + fw_attributes.options = self.ugraph.options + + -- generate new force class + local social_gravity = ForceController.new(self.ugraph, fw_attributes) + + -- add all required forces + social_gravity:addForce{ + force_type = ForceCanvasDistance, + fun_u = function (data) return 4*data.k/(data.d*data.d) end, + time_fun = time_fun_2, + epoch = {"after expand", "during expand"} + } + social_gravity:addForce{ + force_type = ForceCanvasPosition, + fun_u = function (data) return data.attributes[data.u].social_mass*data.attributes.options.gravity end, + time_fun = time_fun_1, + epoch = {"after expand", "during expand"} + } + social_gravity:addForce{ + force_type = ForceGraphDistance, + fun_u = function (data) return -data.d/(data.k*data.k) end, + n = 1, + time_fun = time_fun_3, + epoch = {"after expand", "during expand"} + } + + -- run algorithm + social_gravity:run() +end + +return SocialClass
\ No newline at end of file diff --git a/graphics/pgf/base/tex/generic/graphdrawing/lua/pgf/gd/force/jedi/base/CoarseGraphFW.lua b/graphics/pgf/base/tex/generic/graphdrawing/lua/pgf/gd/force/jedi/base/CoarseGraphFW.lua new file mode 100644 index 0000000000..0487214654 --- /dev/null +++ b/graphics/pgf/base/tex/generic/graphdrawing/lua/pgf/gd/force/jedi/base/CoarseGraphFW.lua @@ -0,0 +1,264 @@ +-- Copyright 2014 by Ida Bruhns +-- +-- This file may be distributed and/or modified +-- +-- 1. under the LaTeX Project Public License and/or +-- 2. under the GNU Public License +-- +-- See the file doc/generic/pgf/licenses/LICENSE for more information + + +--- A class for creating and handling "coarse" versions of a graph. Such versions contain +-- less nodes and edges than the original graph while retaining the overall +-- structure. This class offers functions to create coarse graphs and to expand them +-- to regain their original size. + +-- Imports +local Digraph = require "pgf.gd.model.Digraph" +local Vertex = require "pgf.gd.model.Vertex" +local Arc = require "pgf.gd.model.Arc" + +local lib = require "pgf.gd.lib" + +local CoarseGraph = Digraph.new() +CoarseGraph.__index = CoarseGraph + +--- Creates a new coarse graph derived from an existing graph. +-- +-- Generates a coarse graph for the input |Digraph|. +-- +-- Coarsening describes the process of reducing the amount of vertices in a graph +-- by merging vertices into pseudo-vertices. There are different strategies, +-- to decide which vertices should be merged, like merging vertices that belong to edges in a +-- maximal independent edge set or by creating pseudo-vertices based on a maximal +-- independent node set. Those strategies are called +-- schemes. +-- +-- Coarsening is not performed automatically. The function |CoarseGraph:coarsen| +-- can be used to further coarsen the graph, or the function |CoarseGraph:uncoarsen| +-- can be used to restore the previous state. +-- +-- Note, however, that the input \meta{graph} is always modified in-place, so +-- if the original version of \meta{graph} is needed in parallel to its +-- coarse representations, a deep copy of \meta{graph} needs to be passed over +-- to |CoarseGraph.new|. +-- +-- @param graph An existing graph that needs to be coarsened. +-- @param fw_attributes The user defined attributes, possibly attached to vertices. + +function CoarseGraph.new(ugraph, fw_attributes) + local coarse_graph = { + ugraph = ugraph, + level = 0, + scheme = CoarseGraph.coarsen_independent_edges, + ratio = 0, + fw_attributes = fw_attributes, + collapsed_vertices = {} + } + setmetatable(coarse_graph, CoarseGraph) + return coarse_graph +end + +-- locals for performance +local find_maximal_matching, arc_function + +-- This function performs one coarsening step: It finds all independent vertex +-- set according to |scheme|, coarsens them and adds the newly created +-- vertices to the collapsed_vertices table, associating them with the current +-- level. +function CoarseGraph:coarsen() + -- update the level + self.level = self.level + 1 + + local vertices = self.ugraph.vertices + local old_graph_size = #vertices + local c = {} + local fw_attributes = self.fw_attributes + local ugraph = self.ugraph + + if self.scheme == CoarseGraph.coarsen_independent_edges then + local matching = find_matching(ugraph) + local collapse_vertex + + for _,arc in ipairs(matching) do + -- get the two nodes of the edge that we are about to collapse + local a_h = arc.head + local a_t = arc.tail + local collapse_vertices = {a_h, a_t} + collapse_vertex = Vertex.new {weight = 0, mass = 0} + + ugraph:collapse(collapse_vertices, + collapse_vertex, + function (a,b) + a.weight = a.weight + b.weight + a.mass = a.mass + b.mass + if fw_attributes then + for key,value in pairs(fw_attributes[b]) do + if fw_attributes.functions[key] then + fw_attributes.functions[key](a,b) + elseif type(value) == "number" then + local tmp = fw_attributes[a] + if not tmp[key] then + tmp[key] = 0 + end + tmp[key] = tmp[key] + value + end + end + end + end, + function (a,b) + if a.weight == nil then + a.weight = b.weight + else + a.weight = a.weight + b.weight + end + end) + + local c_v_p = collapse_vertex.pos + local a_h_p = a_h.pos + local a_t_p = a_t.pos + c_v_p.x = (a_h_p.x + a_t_p.x)/2 + c_v_p.y = (a_h_p.y + a_t_p.y)/2 + + c[#c+1] = collapse_vertex + ugraph:remove{a_h, a_t} + end + + -- Enter all collapsed vertices into a table to uncoarsen one level at a time + self.collapsed_vertices[self.level] = c + else + assert(false, 'schemes other than CoarseGraph.coarsen_independent_edges are not implemented yet') + end + -- calculate the number of nodes ratio compared to the previous graph + self.ratio = #vertices / old_graph_size +end + +-- This function expands all vertices associated with the current level, then +-- updates the level. +function CoarseGraph:uncoarsen() + local a = self.collapsed_vertices[self.level] + local ugraph = self.ugraph + local random = lib.random + local randomseed = lib.randomseed + + for j=#a,1,-1 do + randomseed(42) + local to_expand = a[j] + + ugraph:expand(to_expand, function(a,b) + b.pos.x = a.pos.x + random()*10 + b.pos.y = a.pos.y + random()*10 + end) + ugraph:remove{to_expand} + ugraph:sync() + end + + self.level = self.level - 1 +end + +-- Getters +function CoarseGraph:getSize() + return #self.ugraph.vertices +end + + +function CoarseGraph:getRatio() + return self.ratio +end + + +function CoarseGraph:getLevel() + return self.level +end + + +function CoarseGraph:getGraph() + return self.ugraph +end + +-- Private helper function to determine whether the second vertex in the +-- current arc has been matched already +-- +-- @param arc The arc in question +-- @param vertex One of the arc's endpoints, either head or tail +-- @param matched_vertices The table holding all matched vertices +-- +-- @return The arc if the other endpoint has not been matched yet +function arc_function (arc, vertex, matched_vertices) + local x + if arc.head ~= vertex then + x = arc.head + else + x = arc.tail + end + if not matched_vertices[x] then + return arc + end +end + +-- The function finding a maximum matching of independent arcs. +-- +-- @param ugraph The current graph +-- +-- @return A table of arcs which are in the matching +function find_matching(ugraph) + local matching = {} + local matched_vertices = {} + local unmatched_vertices = {} + local vertices = ugraph.vertices + + -- iterate over nodes in random order + for _,j in ipairs(lib.random_permutation(#vertices)) do + local vertex = vertices[j] + -- ignore nodes that have already been matched + if not matched_vertices[vertex] then + local arcs = {} + local all_arcs = {} + for _,v in pairs(ugraph:incoming(vertex)) do all_arcs[#all_arcs+1] = v end + for _,v in pairs(ugraph:outgoing(vertex)) do all_arcs[#all_arcs+1] = v end + -- mark the node as matched + matched_vertices[vertex] = true + + for _, a in ipairs(all_arcs) do + arcs[#arcs +1] = arc_function(a, vertex, matched_vertices) + end + + if #arcs > 0 then + -- sort edges by the weights of the adjacent vertices + table.sort(arcs, function (a, b) + local x, y + if a.head == vertex then + x = a.tail + else + x = a.head + end + if b.head == vertex then + y = b.tail + else + y = b.head + end + return x.weight < y.weight + end) + + -- match the node against the neighbor with minimum weight + matched_vertices[arcs[1].head] = true + matched_vertices[arcs[1].tail] = true + table.insert(matching, arcs[1]) + end + end + end + + -- generate a list of nodes that were not matched at all + for _,j in ipairs(lib.random_permutation(#vertices)) do + local vertex = vertices[j] + if not matched_vertices[vertex] then + table.insert(unmatched_vertices, vertex) + end + end + return matching +end + + +-- done + +return CoarseGraph diff --git a/graphics/pgf/base/tex/generic/graphdrawing/lua/pgf/gd/force/jedi/base/ForceController.lua b/graphics/pgf/base/tex/generic/graphdrawing/lua/pgf/gd/force/jedi/base/ForceController.lua new file mode 100644 index 0000000000..ab30ada5b3 --- /dev/null +++ b/graphics/pgf/base/tex/generic/graphdrawing/lua/pgf/gd/force/jedi/base/ForceController.lua @@ -0,0 +1,489 @@ +-- Copyright 2014 by Ida Bruhns +-- +-- This file may be distributed and/or modified +-- +-- 1. under the LaTeX Project Public License and/or +-- 2. under the GNU Public License +-- +-- See the file doc/generic/pgf/licenses/LICENSE for more information + + +--- This class is the most basic class for the Jedi framework. It manages the +-- forces, epochs, options and streamlines the graph drawing process. +-- In detail, the force template will do the following: +-- % +-- \begin{itemize} +-- \item Hold the table with all epochs currently defined, and provide +-- a function to add new ones +-- \item Hold the table associating forces with the epochs, and provide a +-- function to add new ones +-- \item Define all the non-algorithm-specific options provided by Jedi +-- \item Assert user options to catch exceptions +-- \item Save user options and library functions to local variables to enhance +-- runtime. +-- \item Add any forces that are indicated by set options +-- \item Find and call the initial positioning algorithm requested +-- \item Determine if coarsening is enabled, and manage coarsening process if so +-- \item Call the preprocessing function of each force to obtain a vertex list the +-- force will be applied to +-- \item Calculate the forces affecting each vertex. +-- \item Move the vertices, check for equilibria/used up iterations, update +-- virtual time +-- \end{itemize} + +local ForceController = {} +ForceController.__index = ForceController + +-- Imports +local declare = require "pgf.gd.interface.InterfaceToAlgorithms".declare +local Coordinate = require "pgf.gd.model.Coordinate" +local CoarseGraph = require 'pgf.gd.force.jedi.base.CoarseGraphFW' +local PriorityQueue = require "pgf.gd.lib.PriorityQueue" +local ForcePullToPoint = require "pgf.gd.force.jedi.forcetypes.ForcePullToPoint" +local ForcePullToGrid = require "pgf.gd.force.jedi.forcetypes.ForcePullToGrid" + +local epochs = { + [1] = "preprocessing", + [2] = "initial layout", + [3] = "start coarsening process", + [4] = "before coarsen", + [5] = "start coarsen", + [6] = "during coarsen", + [7] = "end coarsen", + [8] = "before expand", + [9] = "start expand", + [10] = "during expand", + [11] = "end expand", + [12] = "end coarsening process", + [13] = "after expand", + [14] = "postprocessing" +} + +-- Automatic parameter generation for epoch-variables +for _,e in ipairs(epochs) do + --- + declare { + key = "iterations " .. e, + type = "number" + } + + --- + declare { + key = "maximum displacement per step " .. e, + type = "number" + } + + --- + declare { + key = "global speed factor " .. e, + type = "length" + } + + --- + declare { + key = "maximum time " .. e, + type = "number" + } + + --- + declare { + key = "find equilibrium ".. e, + type = "boolean" + } + + --- + declare { + key = "equilibrium threshold ".. e, + type = "number" + } +end + +-- Implementation starts here + +--- Function allowing user to add an at the specified position +-- +-- @params epoch A string that names the epoch +-- @params position The position in the epoch array at which the epoch should be inserted + +function ForceController:addEpoch(epoch, position) + table.insert(epochs, position, epoch) +end + +--- Function allowing the user to find an epoch's position in the epoch table +-- +-- @params epoch The epoch who's position we are trying to find +-- +-- @return An integer value matching the epch's index, or $-1$ if epoch was not found + +function ForceController:findEpoch(epoch) + for j, e in ipairs(epochs) do + if e == epoch then + return j + end + end + return -1 +end + + +-- locals for performance +local net_forces = {} +local sqrt = math.sqrt +local abs = math.abs +local sum_up, options, move_vertices, get_net_force, preprocessing, epoch_forces + +--- Creating a new force algorithm +-- @params ugraph The ugraph object the graph drawing algorithm will run on +-- @params fw_attributes The storage object holding the additional attributes defined by +-- the engineer +-- +-- @returns A new instance of force template +function ForceController.new(ugraph, fw_attributes) + return setmetatable( + {epoch_forces = {}, + ugraph = ugraph, + fw_attributes = fw_attributes, + pull_to_point = false, + }, ForceController) +end + +--- Running the force algorithm + +function ForceController:run() + -- locals for performance + local ugraph = self.ugraph + local coarse_graph = CoarseGraph.new(ugraph, self.fw_attributes) + local vertices_initalized = false + options = ugraph.options + epoch_forces = self.epoch_forces + local minimum_graph_size = options["minimum coarsening size"] + local vertices = ugraph.vertices + local arcs = ugraph.arcs + local downsize_ratio = options["downsize ratio"] + local natural_spring_length = options["node distance"] + local snap_to_grid = options["snap to grid"] + local coarsen = options["coarsen"] + + -- Assert user input + assert(minimum_graph_size >= 2, 'the minimum coarsening size of coarse graphs (value: ' .. minimum_graph_size .. ') needs to be greater than or equal to 2') + assert(downsize_ratio >= 0 and downsize_ratio <=1, 'the downsize ratio of the coarse graphs (value: ' .. downsize_ratio .. ') needs to be greater than or equal to 0 and smaller than or equal to 1') + assert(natural_spring_length >= 0, 'the node distance (value: ' .. natural_spring_length .. ') needs to be greater than or equal to 0') + + -- initialize vertex and arc weights + for _,vertex in ipairs(vertices) do + vertex.weight = vertex.options["coarsening weight"] + vertex.mass = vertex.options.mass + end + + for _,arc in ipairs(arcs) do + arc.weight = 1 + end + + -- Initialize epoch_forces table entries as empty tables + for _, e in ipairs(epochs) do + if not self.epoch_forces[e] then + self.epoch_forces[e] = {} + end + end + + -- Find initial positioning algorithm + local initial_positioning_class = options.algorithm_phases['initial positioning force framework'] -- initial_types[self.initial_layout] + + -- If snap to grid option is set and no force was added yet, add an extra + -- force to post-processing + if snap_to_grid then + self:addForce{ + force_type = ForcePullToGrid, + cap = 1, + time_fun = function() return 40 end, + epoch = {"postprocessing"} + } + options["iterations postprocessing"] = options["iterations postprocessing"] or 200 + options["maximum time postprocessing"] = options["maximum time postprocessing"] or 200 + options["find equilibrium postprocessing"] = options["find equilibrium postprocessing"] or true + options["equilibrium threshold postprocessing"] = options["equilibrium threshold postprocessing"] or 1 + options["maximum displacement per step postprocessing"] = options["maximum displacement per step postprocessing"] or 1 + options["global speed factor postprocessing"] = options["global speed factor postprocessing"] or 1 + end + + -- Find marker epochs + local start_coarsening = self:findEpoch("start coarsening process") + local end_coarsening = self:findEpoch("end coarsening process") + local start_coarsen = self:findEpoch("start coarsen") + local end_coarsen = self:findEpoch("end coarsen") + local start_expand = self:findEpoch("start expand") + local end_expand = self:findEpoch("end expand") + + + -- iterate over epoch table + local i = 1 + while i <= #epochs do + local e = epochs[i] + + local iterations = options["iterations "..e] or options["iterations"] + -- assert input + assert(iterations >= 0, 'iterations (value: ' .. iterations .. ') needs to be greater than 0') + + -- Check for desired vertices and collect them in a table if any are found + local desired = false + local desired_vertices = {} + -- initialize node weights + for _,vertex in ipairs(vertices) do + if vertex.options then + if vertex.options["desired at"] then + desired = true + desired_vertices[vertex] = vertex.options["desired at"] + end + end + end + + -- Add pull to point force if desired vertices were found and engineer did not add + -- this force + if desired and not self.pull_to_point then + self:addForce{ + force_type = ForcePullToPoint, + time_fun = function(t_now, t_max) return 5 end + } + end + + -- initialize the coarse graph data structure. + if coarsen then + -- vertices = coarse_graph.ugraph.vertices + -- arcs = coarse_graph.ugraph.arcs + if i >= start_coarsening and i < end_coarsening then + -- coarsen the graph repeatedly until only minimum_graph_size nodes + -- are left or until the size of the coarse graph was not reduced by + -- at least the downsize ratio configured by the user + if i >= start_coarsen and i < start_expand then + if coarse_graph:getSize() > minimum_graph_size and coarse_graph:getRatio() <= (1 - downsize_ratio) then + if i == start_coarsen then + coarse_graph:coarsen() + elseif i < end_coarsen then + preprocessing(coarse_graph.ugraph.vertices, coarse_graph.ugraph.arcs, e, coarse_graph.ugraph) + move_vertices(coarse_graph.ugraph.vertices, e) + else + i = start_coarsen - 1 + end + end + end + + -- between coarsening and expanding + if (i > end_coarsen) and (i < start_expand) then + -- use the natural spring length as the initial natural spring length + local spring_length = natural_spring_length + + if not vertices_initalized then + initial_positioning_class.new { vertices = coarse_graph.ugraph.vertices, + options = options, + desired_vertices = desired_vertices + }:run() + vertices_initalized = true + end + + preprocessing(coarse_graph.ugraph.vertices, coarse_graph.ugraph.arcs, e, coarse_graph.ugraph) + + -- set the spring length to the average arc length of the initial layout + local spring_length = 0 + for _,arc in ipairs(arcs) do + local x = abs(arc.head.pos.x - arc.tail.pos.x) + local y = abs(arc.head.pos.y - arc.tail.pos.y) + spring_length = spring_length + sqrt(x * x + y * y) + end + spring_length = spring_length / #arcs + + -- additionally improve the layout with the force-based algorithm + -- if there are more than two nodes in the coarsest graph + if coarse_graph:getSize() > 2 and end_coarsen and not start_expand then + move_vertices(coarse_graph.ugraph.vertices, e) + end + end + + -- undo coarsening step by step, applying the force-based sub-algorithm + -- to every intermediate coarse graph as well as the original graph + if i >= start_expand then + if coarse_graph:getLevel() > 0 then + if i == start_expand then + coarse_graph:uncoarsen() + elseif i < end_expand then + preprocessing(coarse_graph.ugraph.vertices, coarse_graph.ugraph.arcs, e, coarse_graph.ugraph) + move_vertices(coarse_graph.ugraph.vertices, e) + else + i = start_expand - 1 + end + else + preprocessing(coarse_graph.ugraph.vertices, coarse_graph.ugraph.arcs, e, coarse_graph.ugraph) + move_vertices(coarse_graph.ugraph.vertices, e) + end + end + -- Before and after the coarsening process + elseif i < start_coarsening or i > end_coarsening then + if not vertices_initalized then + initial_positioning_class.new { + vertices = coarse_graph.ugraph.vertices, + options = options, + desired_vertices = desired_vertices }:run() + vertices_initalized = true + end + preprocessing(coarse_graph.ugraph.vertices, coarse_graph.ugraph.arcs, e, coarse_graph.ugraph) + move_vertices(coarse_graph.ugraph.vertices, e) + end + else + -- Same without coarsen + if i < start_coarsening or i > end_coarsening then + if not vertices_initalized then + initial_positioning_class.new { + vertices = vertices, + options = options, + desired_vertices = desired_vertices }:run() + vertices_initalized = true + end + preprocessing(vertices, arcs, e, ugraph) + move_vertices(vertices, e, self.ugraph) + end + end + i = i + 1 + end +end + + +--- Preprocessing for all force types in force configuration +-- +-- @params v The vertices of the current graph +-- @params a The arcs of the current graph +-- @params epoch The preprocessing algorithm will only be applied to the forces +-- associated with this epoch. +-- @params ugraph The current graph object + +function preprocessing(v, a, epoch, ugraph) + for _, fc in ipairs(epoch_forces[epoch]) do + fc:preprocess(v, a, ugraph) + end +end + + +--- Adding forces to the algorithm. +-- +-- @params force_data A table containing force type, time function, force function, +-- capping thresholds and the epochs in which this force will be active + +function ForceController:addForce(force_data) + local t = force_data.force_type + if t == ForcePullToPoint then + self.pull_to_point = true + end + + local f = t.new {force = force_data, options = self.ugraph.options, fw_attributes = self.fw_attributes or {}} + if force_data.epoch == nil then + force_data.epoch = {} + end + for _,e in ipairs(force_data.epoch) do + local tab = self.epoch_forces[e] + if not tab then + tab = {} + end + tab[#tab +1] = f + self.epoch_forces[e] = tab + end +end + + +--- Moving vertices according to force functions until the maximum number of +-- iterations is reached +-- +-- @params vertices The vertices in the current graph +-- @params epoch The current epoch, to find the forces that are active + +function move_vertices(vertices, epoch, g) + if #epoch_forces[epoch] == 0 then + return + end + local iterations = options["iterations ".. epoch] or options["iterations"] + local find_equilibrium = options["find equilibrium ".. epoch] or options["find equilibrium"] + local epsilon = options["equilibrium threshold ".. epoch] or options["equilibrium threshold"] + local speed = options["global speed factor ".. epoch] or options["global speed factor"] + local max_step = options["maximum displacement per step ".. epoch] or options["maximum displacement per step"] + + assert(epsilon >= 0, 'the threshold for finding an equilibirum (equilibrium threshold) (value: ' .. epsilon .. ') needs to be greater than or equal to 0') + assert(speed > 0, 'the speed at which the vertices move (value: ' .. speed .. ') needs to be greater than 0') + assert(max_step > 0, 'the maximum displacement per step each vertex can move per iteration (value: ' .. max_step .. ') needs to be greater than 0') + + local max_time = options["maximum time ".. epoch] or options["maximum time"] + local d_t = max_time/iterations + local t_now = 0 + local random = lib.random + local randomseed = lib.randomseed + + for j = 1 , iterations do + t_now = t_now + d_t + net_forces = get_net_force(vertices, j, t_now, epoch) + + -- normalize the force vector if necessary + for v, c in pairs(net_forces) do + local n = sqrt(c.x*c.x+c.y*c.y) + if n > max_step then + local factor = max_step/n + c.x = c.x*factor + c.y = c.y*factor + end + end + + -- if not in equilibrium yet, apply forces + if not find_equilibrium or sum_up(net_forces)*d_t > epsilon then + local cool_down_dt = d_t + if cool_down_dt > 1 then + cool_down_dt = 1 + 1/d_t + end + for _, v in ipairs(vertices) do + local factor = 1/(v.mass or 1) + local c1 = net_forces[v] + local x = speed * cool_down_dt * c1.x * factor + local y = speed * cool_down_dt * c1.y * factor + local p = v.pos + p.x = p.x + x + p.y = p.y + y + end + else + break + end + end +end + + +-- calculate the net force for each vertex in one iteration +-- +-- @params vertices the vertices of the current graph +-- @params j The current iteration +-- @params t_now The current virtual time +-- @params epoch The current epoch +-- +-- @return A table of coordinate-objects associated with vertices. The +-- coordinate object hold the calculated net displacement for +-- the $x$ and $y$ coordinate. +function get_net_force(vertices, j, t_now, epoch) + local net_forces = {} + local natural_spring_length = options["node distance"] + + for _,v in ipairs(vertices) do + net_forces[v] = Coordinate.new(0,0) + end + + for _,force_class in ipairs(epoch_forces[epoch]) do + force_class:applyTo{net_forces = net_forces, options = options, j = j, t_now = t_now, k = natural_spring_length} + end + + return net_forces +end + +-- Helper function to sum up all calculated forces +-- +-- @params tab A table holding coordinate objects as values +-- +-- @returns The sum of the absolute $x$ and $y$ values in this table +function sum_up(tab) + local sum = 0 + for v, c in pairs(tab) do + sum = sum + abs(c.x) + abs(c.y) + end + return sum +end + +return ForceController diff --git a/graphics/pgf/base/tex/generic/graphdrawing/lua/pgf/gd/force/jedi/base/ForceTemplate.lua b/graphics/pgf/base/tex/generic/graphdrawing/lua/pgf/gd/force/jedi/base/ForceTemplate.lua new file mode 100644 index 0000000000..29370fc029 --- /dev/null +++ b/graphics/pgf/base/tex/generic/graphdrawing/lua/pgf/gd/force/jedi/base/ForceTemplate.lua @@ -0,0 +1,44 @@ +-- Copyright 2014 by Ida Bruhns +-- +-- This file may be distributed and/or modified +-- +-- 1. under the LaTeX Project Public License and/or +-- 2. under the GNU Public License +-- +-- See the file doc/generic/pgf/licenses/LICENSE for more information + + +--- This is the parent class for forces. It provides a constructor and methods +-- stubs to be overwritten in the subclasses. + +-- Imports +local lib = require "pgf.gd.lib" + +local ForceTemplate = lib.class {} + +-- constructor +function ForceTemplate:constructor() + self.force = self.force + self.fw_attributes = self.fw_attributes + if not self.force.time_fun then + self.force.time_fun = function() return 1 end + end +end + +-- Method stub for preprocessing +-- +-- @param v The vertices the list will be build on + +function ForceTemplate:preprocess(v) +end + +-- Method stub for applying the forces +-- +-- @param data A table holding data like the table the forces are collected +-- in, the current iteration, the current time stamp, some options +-- or the natural spring length + +function ForceTemplate:applyTo(data) +end + +return ForceTemplate
\ No newline at end of file diff --git a/graphics/pgf/base/tex/generic/graphdrawing/lua/pgf/gd/force/jedi/base/InitialTemplate.lua b/graphics/pgf/base/tex/generic/graphdrawing/lua/pgf/gd/force/jedi/base/InitialTemplate.lua new file mode 100644 index 0000000000..762717f64a --- /dev/null +++ b/graphics/pgf/base/tex/generic/graphdrawing/lua/pgf/gd/force/jedi/base/InitialTemplate.lua @@ -0,0 +1,64 @@ +-- Copyright 2014 by Ida Bruhns +-- +-- This file may be distributed and/or modified +-- +-- 1. under the LaTeX Project Public License and/or +-- 2. under the GNU Public License +-- +-- See the file doc/generic/pgf/licenses/LICENSE for more information + + +--- This is the parent class for initial layout algorithms. It provides a +-- constructor and methods stubs to be overwritten in the subclasses as well +-- as placing vertices which are |desired at| a certain point. + +-- Imports +local lib = require "pgf.gd.lib" + +local InitialTemplate = lib.class {} + +-- constructor +function InitialTemplate:constructor() + self.vertices = self.vertices + self.options = self.options + self.desired_vertices = self.desired_vertices +end + +-- Method placing |desired at| vertices at the point they are desired +-- +-- @params desired_vertices A table containing all the vertices where the +-- |desired at| option is set. +-- +-- @return |placed| A boolean array stating if vertices have been placed yet +-- @return |centroid_x| The x-coordinate of the midpoint of all placed vertices +-- @return |centroid_y| The y-coordinate of the midpoint of all placed vertices + +function InitialTemplate:desired(desired_vertices) + local placed = {} + + local centroid_x, centroid_y = 0, 0 + + local size = 0 + for v, da in pairs(desired_vertices) do + local p = v.pos + local x, y = da.x, da.y + p.x = x or 0 + p.y = y or 0 + centroid_x = centroid_x + x + centroid_y = centroid_y + y + placed[v] = true + size = size +1 + end + if size>0 then + centroid_x = centroid_x / size + centroid_y = centroid_y / size + end + + return placed, centroid_x, centroid_y +end + +-- Method stub for running the layout algorithm +function InitialTemplate:run() +end + +return InitialTemplate
\ No newline at end of file diff --git a/graphics/pgf/base/tex/generic/graphdrawing/lua/pgf/gd/force/jedi/base/PathLengthsFW.lua b/graphics/pgf/base/tex/generic/graphdrawing/lua/pgf/gd/force/jedi/base/PathLengthsFW.lua new file mode 100644 index 0000000000..2d50677fa5 --- /dev/null +++ b/graphics/pgf/base/tex/generic/graphdrawing/lua/pgf/gd/force/jedi/base/PathLengthsFW.lua @@ -0,0 +1,174 @@ +-- Copyright 2014 by Ida Bruhns +-- +-- This file may be distributed and/or modified +-- +-- 1. under the LaTeX Project Public License and/or +-- 2. under the GNU Public License +-- +-- See the file doc/generic/pgf/licenses/LICENSE for more information + +--- This is a helper class providing different functions that deal with graph +-- distances. This class can be used by engineers and implementers if they +-- need to calculate anything regarding graph distances. + +local PathLengths = {} + +-- Imports +local PriorityQueue = require "pgf.gd.lib.PriorityQueue" +local Preprocessing = require "pgf.gd.force.jedi.base.Preprocessing" + +-- This algorithm conducts a breadth first search on the graph it is given. +-- +-- @param ugraph The graph on which the search should be conducted +-- +-- @return A table holding every vertex $v$ as key and a table as value. The +-- value table holds all other vertices $u$ as keys and their shortest +-- distance to $v$ as value + +function PathLengths:breadthFirstSearch(ugraph) + local distances = {} + local vertices = ugraph.vertices + local arcs = ugraph.arcs + + for _,v in ipairs(vertices) do + distances[v] = {} + local dist = distances[v] + for _,w in ipairs(vertices) do + dist[w] = #vertices +1 + end + dist[v] = 0 + end + local n = 1 + local p = Preprocessing.overExactlyNPairs(vertices, arcs, n) + while (#p > 0) do + for _, v in ipairs(p) do + local tab = distances[v.tail] + tab[v.head] = n + end + n = n + 1 + p = Preprocessing.overExactlyNPairs(vertices, arcs, n) + end + return(distances) +end + + +-- This function performs Dijkstra's algorithm on the graph. +-- +-- @param ugraph The graph where the paths should be found +-- @param source The source vertex +-- +-- @return |distance| A table holding every vertex $v$ as key and a table as +-- value. The value table holds all other vertices $u$ as +-- keys and their shortest distance to $v$ as value +-- @return |levels| A table holding the levels of the graph as keys and a +-- table holding the vertices found on that level as values +-- @return |parent| A table holding each vertex as key and it's parent vertex +-- as value + +function PathLengths:dijkstra(ugraph, source) + local distance = {} + local levels = {} + local parent = {} + + local queue = PriorityQueue.new() + + -- reset the distance of all nodes and insert them into the priority queue + for _,v in ipairs(ugraph.vertices) do + if v == source then + distance[v] = 0 + parent[v] = nil + queue:enqueue(v, distance[v]) + else + distance[v] = #ugraph.vertices + 1 -- this is about infinity ;) + queue:enqueue(v, distance[v]) + end + end + + while not queue:isEmpty() do + local u = queue:dequeue() + + assert(distance[u] < #ugraph.vertices + 1, 'the graph is not connected, Dijkstra will not work') + + if distance[u] > 0 then + levels[distance[u]] = levels[distance[u]] or {} + table.insert(levels[distance[u]], u) + end + + + + for _,edge in ipairs(ugraph:outgoing(u)) do + local v = edge.head + local alternative = distance[u] + 1 + if alternative < distance[v] then + distance[v] = alternative + + parent[v] = u + + -- update the priority of v + queue:updatePriority(v, distance[v]) + end + end + end + + return distance, levels, parent +end + +-- This function finds the pseudo diameter of the graph, which is the longest +-- shortest path in the graph +-- +-- @param ugraph The graph who's pseudo diameter is wanted +-- +-- @ return |diameter| The pseudo diameter of the graph +-- @ return |start_node| The start node of the longest shortest path in the +-- graph +-- @ return |end_node| The end node of the longest shortest path in the graph + +function PathLengths:pseudoDiameter(ugraph) + + -- find a node with minimum degree + local start_node = ugraph.vertices[1] + for _,v in ipairs(ugraph.vertices) do + if #ugraph:incoming(v) + #ugraph:outgoing(v) < #ugraph:incoming(start_node) + #ugraph:outgoing(start_node) then + start_node = v + end + end + + assert(start_node) + + local old_diameter = 0 + local diameter = 0 + local end_node = nil + + while true do + local distance, levels = self:dijkstra(ugraph, start_node) + + -- the number of levels is the same as the distance of the nodes + -- in the last level to the start node + old_diameter = diameter + diameter = #levels + + -- abort if the diameter could not be improved + if diameter == old_diameter then + end_node = levels[#levels][1] + break + end + + -- select the node with the smallest degree from the last level as + -- the start node for the next iteration + start_node = levels[#levels][1] + for _,node in ipairs(levels[#levels]) do + if #ugraph:incoming(node)+#ugraph:outgoing(node) < #ugraph:incoming(start_node) + #ugraph:outgoing(start_node) then + start_node = node + end + end + + assert(start_node) + end + + assert(start_node) + assert(end_node) + + return diameter, start_node, end_node +end + +return PathLengths
\ No newline at end of file diff --git a/graphics/pgf/base/tex/generic/graphdrawing/lua/pgf/gd/force/jedi/base/Preprocessing.lua b/graphics/pgf/base/tex/generic/graphdrawing/lua/pgf/gd/force/jedi/base/Preprocessing.lua new file mode 100644 index 0000000000..de59e0cb5c --- /dev/null +++ b/graphics/pgf/base/tex/generic/graphdrawing/lua/pgf/gd/force/jedi/base/Preprocessing.lua @@ -0,0 +1,122 @@ +-- Copyright 2014 by Ida Bruhns +-- +-- This file may be distributed an/or modified +-- +-- 1. under the LaTeX Project Public License and/or +-- 2. under the GNU Public License +-- +-- See the file doc/generic/pgf/licenses/LICENSE for more information + + +--- This file holds functions to create lists of vertex pairs. All +-- functions return a Graph object containing the vertices of the +-- original graph and an edge between the vertices forming a pair +-- under the specified conditions. The lists can be precomputed to +-- enhance performance. + +local PreprocessClass = {} + +-- Imports +local declare = require "pgf.gd.interface.InterfaceToAlgorithms".declare +local Digraph = require "pgf.gd.model.Digraph" + + +-- Creates a graph object with an arc between all pairwise disjoint vertex +-- pairs and returns the arc table +-- +-- @param vertices The vertices of the original graph +-- +-- @return An arc table + +function PreprocessClass.allPairs(vertices) + local aP = Digraph.new{} + for _, vertex in ipairs(vertices) do + for _, vertex2 in ipairs(vertices) do + if vertex ~= vertex2 then + if not aP:contains(vertex) then + aP:add {vertex} + end + if not aP:contains(vertex2) then + aP:add {vertex2} + end + if not aP:arc(vertex, vertex2) and not aP:arc(vertex2, vertex) then + aP:connect(vertex, vertex2) + end + end + end + end + return aP.arcs +end + + +-- Creates a graph object with an arc between all pairwise disjoint vertex +-- pairs that are connected by a shortest path of length n in the original +-- graph and returns the arc table +-- +-- @param vertices The vertices of the original graph +-- @param arcs The arcs of the original graph +-- @param n The length of the shortest path we are looking for +-- +-- @return An arc table + +function PreprocessClass.overExactlyNPairs(vertices, arcs, n) + local waste, p_full = PreprocessClass.overMaxNPairs(vertices, arcs, n) + local waste, p_small = PreprocessClass.overMaxNPairs(vertices, arcs, n-1) + for _, paar in ipairs(p_full.arcs) do + if p_small:arc(paar.head, paar.tail) ~= nil or p_small:arc(paar.tail, paar.head) ~= nil then + p_full:disconnect(paar.head, paar.tail) + p_full:disconnect(paar.tail, paar.head) + end + end + return p_full.arcs +end + + +-- Creates a graph object with an arc between all pairwise disjoint vertex +-- pairs that are connected by a shortest path of length n or shorter in the +-- original graph and returns the arc table +-- +-- @param vertices The vertices of the original graph +-- @param arcs The arcs of the original graph +-- @param n The length of the shortest path we are looking for +-- +-- @return An arc table + +function PreprocessClass.overMaxNPairs(vertices, arcs, n) + assert(n >= 0, 'n (value: ' .. n.. ') needs to be greater or equal 0') + local p = Digraph.new{} + local oneHop = Digraph.new{} + if n> 0 then + for _, arc in ipairs(arcs) do + local vertex = arc.head + local vertex2 = arc.tail + if not p:contains(vertex) then + p:add {vertex} + oneHop:add {vertex} + end + if not p:contains(vertex2) then + p:add {vertex2} + oneHop:add {vertex2} + end + if p:arc(vertex, vertex2) == nil and p:arc(vertex2, vertex) == nil then + p:connect(vertex, vertex2) + oneHop:connect(vertex, vertex2) + end + end + end + + n = n-1 + while n > 0 do + for _, paar in ipairs(p.arcs) do + for _, vertex in ipairs(vertices) do + if paar.head ~= vertex and p:arc(paar.head, vertex) == nil and p:arc(vertex, paar.head) == nil and (oneHop:arc(paar.tail, vertex) ~= nil or oneHop:arc(vertex, paar.tail) ~= nil) then + p:connect(paar.head, vertex) + end + end + end + n = n-1 + end + return p.arcs, p +end + +return PreprocessClass diff --git a/graphics/pgf/base/tex/generic/graphdrawing/lua/pgf/gd/force/jedi/doc.lua b/graphics/pgf/base/tex/generic/graphdrawing/lua/pgf/gd/force/jedi/doc.lua new file mode 100644 index 0000000000..19baca7333 --- /dev/null +++ b/graphics/pgf/base/tex/generic/graphdrawing/lua/pgf/gd/force/jedi/doc.lua @@ -0,0 +1,377 @@ +-- Copyright 2014 by Ida Bruhns and Till Tantau +-- +-- This file may be distributed and/or modified +-- +-- 1. under the LaTeX Project Public License and/or +-- 2. under the GNU Public License +-- +-- See the file doc/generic/pgf/licenses/LICENSE for more information + +-- Imports +local key = require 'pgf.gd.doc'.key +local documentation = require 'pgf.gd.doc'.documentation +local summary = require 'pgf.gd.doc'.summary +local example = require 'pgf.gd.doc'.example + + +-------------------------------------------------------------------- +key "maximum step" + +summary +[[ +This option determines the maximum distance every vertex is allowed to travel +in one iteration. +]] + +documentation +[[ +No matter how large the forces influencing a vertex, the effect +on the drawing should be limited to avoid vertices "jumping" from one side of +the canvas to each other due to a strong force pulling them further than their +ideal destination. The amount of space a vertex is allowed to travel in one +iteration is limited by the \lstinline{maximum step} parameter. It is $5000$ +by default. That means by default, this parameter should not get in your way. +]] + + +example +[[ +\tikz + \graph[social degree layout, iterations = 2, maximum time = 2, maximum step = 6pt, coarsen = false]{ + a1 -- {a2, a3, a4, a5}, + b1 -- {b2 -- {b3, b4}, b5} + }; +]] + +example +[[ +\tikz + \graph[social degree layout, iterations = 2, maximum time = 2, maximum step = 12pt, coarsen = false]{ + a1 -- {a2, a3, a4, a5}, + b1 -- {b2 -- {b3, b4}, b5} + }; +]] +-------------------------------------------------------------------- + + + + + +-------------------------------------------------------------------- +key "speed" + +summary +[[ +This is a factor every calculated step is multiplied by. +]] + +documentation +[[ +The speed is the distance a vertex travels if it is influenced by a force of +$1$N$\cdot\gamma$. The speed is only a factor that will influence the total +amount every vertex can move: Half the speed makes half the movement, twice +the speed doubles the distance traveled. +]] + +example +[[ +\tikz + \graph[social degree layout, iterations = 1, maximum time = 1, maximum step = 100, speed = 0.2, coarsen = false]{ + a1 -- {a2, a3, a4, a5}, + b1 -- {b2 -- {b3, b4}, b5} + }; +]] + +example +[[ +\tikz + \graph[social degree layout, iterations = 1, maximum time= 1, maximum step = 100, speed = 0.4, coarsen = false]{ + a1 -- {a2, a3, a4, a5}, + b1 -- {b2 -- {b3, b4}, b5} + }; +]] +-------------------------------------------------------------------- + + + +-------------------------------------------------------------------- +key "maximum time" + +summary +[[ +The highest amount of virtual time the algorithm is allowed to take. +]] + +documentation +[[ +This option is part of the virtual time construct of Jedi. The virtual time +concept allows graph drawing algorithm engineers to switch forces on and of +after a relative or absolute amount of time has elapsed. If the iterations +stay the same, doubling the maximum time has the same effect as doubling the +speed: Vertices move faster, but it is possible they miss their intended +destination. Also increasing the iterations changes the "resolution" of the +graph drawing algorithm: More steps are simulated in the same time. +]] + +example +[[ +\tikz + \graph[social degree layout, iterations = 20, maximum time = 100, coarsen = false, maximum step = 0.5, gravity = 2]{ + a1 -- {a2, a3, a4, a5}, + b1 -- {b2 -- {b3, b4}, b5} + }; +]] + +example +[[ +\tikz + \graph[social degree layout, iterations = 20, maximum time = 200, coarsen = false, maximum step = 0.5, gravity = 2]{ + a1 -- {a2, a3, a4, a5}, + b1 -- {b2 -- {b3, b4}, b5} + }; +]] +-------------------------------------------------------------------- + + + +-------------------------------------------------------------------- +key "find equilibrium" + +summary +[[ +If this option is |true|, the framework checks the vertex movement to detect +low movement near the equilibrium and stop the algorithm. +]] + +documentation +[[ +Since we often do not know how many iterations are enough, the framework will +detect when the vertices (almost) stop moving and stop the algorithm. After +each iteration, the framework adds up the net force influencing all the +vertices. If it falls below the threshold |epsilon|, the algorithm +will ignore the left over iterations and terminate. You can disable this +behavior by setting this parameter to |false|. Allowing the framework to find +the equilibrium usually saves you time, while allowing more iterations (or a +lower threshold) generates higher quality drawings. +]] + +example +[[ +\tikz + \graph[social degree layout, iterations = 300, maximum time = 300, coarsen = false, maximum step = 10, epsilon = 10]{ + a1 -- {a2, a3, a4, a5}, + b1 -- {b2 -- {b3, b4}, b5} + }; +]] + +example +[[ +\tikz + \graph[social degree layout, iterations = 300, maximum time = 300, maximum step = 10, find equilibrium = false]{ + a1 -- {a2, a3, a4, a5}, + b1 -- {b2 -- {b3, b4}, b5} + }; +]] +-------------------------------------------------------------------- + + + +-------------------------------------------------------------------- +key "epsilon" + +summary +[[ +The threshold for the |find equilibrium| option. +]] + +documentation +[[ +This key specifies the threshold for the |find equilibrium| option. The lower +epsilon, the longer the graph drawing algorithm will take, but the closer the +resulting drawing will be to the true energy minimum. +]] + +example +[[ +\tikz + \graph[social degree layout, iterations = 200, maximum time = 200, maximum step = 10, coarsen = false, epsilon = 2]{ + a1 -- {a2, a3, a4, a5}, + b1 -- {b2 -- {b3, b4}, b5} + }; +]] + +example +[[ +\tikz + \graph[social degree layout, iterations = 200, maximum time = 200, maximum step = 10, epsilon = 12, coarsen = false]{ + a1 -- {a2, a3, a4, a5}, + b1 -- {b2 -- {b3, b4}, b5} + }; +]] +-------------------------------------------------------------------- + + + +-------------------------------------------------------------------- +key "snap to grid" + +summary +[[ +This option enables the post-processing step |snap to grid|. +]] + +documentation +[[ +This key is the on/off-switch for the grid forces. The |snap to grid| option +triggers a form of post-processing were all vertices are pulled to the closest +point on a virtual grid. Please note that there is no repulsive force between +the vertices, so it is possible that two vertices are pulled to the same grid +point. The grid size is determined by the parameters |grid x length| and +|grid y length|. +]] + +example +[[ +\tikz + \graph[social degree layout, iterations = 100, maximum time = 100, maximum step = 10]{ + a1 -- {a2, a3, a4, a5}, + b1 -- {b2 -- {b3, b4}, b5} + }; +]] + +example +[[ +\tikz{ + \graph[social degree layout, iterations = 100, maximum time = 100, snap to grid =true, grid x length = 5mm, grid y length = 5mm, maximum step = 10]{ + a1 -- {a2, a3, a4, a5}, + b1 -- {b2 -- {b3, b4}, b5} + }; +]] +-------------------------------------------------------------------- + + + +-------------------------------------------------------------------- +key "grid x length" + +summary +[[ +This option determines the cell size in $x$ direction for the |snap to grid| +option. +]] + +documentation +[[ +The size of the cells of the virtual grid can be configured by the user. This +key allows a configuration of the horizontal cell width. +]] + +example +[[ +\tikz + \graph[social degree layout, iterations = 100, maximum time = 100, snap to grid =true, grid x length = 5mm, grid y length = 5mm, maximum step = 10]{ + a1 -- {a2, a3, a4, a5}, + b1 -- {b2 -- {b3, b4}, b5} + }; +]] + +example +[[ +\tikz + \graph[social degree layout, iterations = 100, maximum time = 100, snap to grid =true, grid x length = 9mm, grid y length = 5mm, maximum step = 10]{ + a1 -- {a2, a3, a4, a5}, + b1 -- {b2 -- {b3, b4}, b5} + }; +]] +-------------------------------------------------------------------- + + + +-------------------------------------------------------------------- +key "grid y length" + +summary +[[ +This option determines the cell size in $x$ direction for the |snap to grid| +option. +]] + +documentation +[[ +Same as |grid x length|, but in vertical direction (height of the cells). +]] + +example +[[ +\tikz + \graph[social degree layout, iterations = 100, maximum time = 100, snap to grid =true, grid x length = 5mm, grid y length = 5mm, maximum step = 10]{ + a1 -- {a2, a3, a4, a5}, + b1 -- {b2 -- {b3, b4}, b5} + }; +]] + +example +[[ +\tikz + \graph[social degree layout, iterations = 100, maximum time = 100, snap to grid =true, grid x length = 5mm, grid y length = 9mm, maximum step = 10]{ + a1 -- {a2, a3, a4, a5}, + b1 -- {b2 -- {b3, b4}, b5} + }; +]] +-------------------------------------------------------------------- + + +-------------------------------------------------------------------- +key "mass" + +summary +[[ + The mass of a vertex determines how fast it can move. Vertices + with higher mass move slower. +]] + +documentation +[[ + The mass of a vertex determines how fast this vertex + moves. Mass is directly inverse proportional to the distance the vertex + moves. In contrast to the global speed factor, mass usually only affects a + single vertex. A vertex with a higher mass will move slower if affected by + the same mass than a vertex with a lower mass. By default, each vertex has a + mass of $1$. +]] + +example +[[ + \tikz + \graph[social degree layout, iterations = 100, maximum time = 100, maximum displacement per step = 10]{ + a1 -- {a2, a3, a4, a5}, + b1 -- {b2 -- {b3, b4}, b5} + }; +]] + +example +[[ + \tikz + \graph[social degree layout, iterations = 100, maximum time = 100, maximum displacement per step = 10]{ + a1 -- {a2, a3, a4, a5}, + b1[mass = 4] -- {b2 -- {b3, b4}, b5} + }; +]] +-------------------------------------------------------------------- + + +-------------------------------------------------------------------- +key "coarsening weight" + +summary +[[ + The coarsening weight of a vertex determines when it will be + coarsened. +]] + +documentation +[[ + Vertices with higher coarsening weight are considered more important and + will be coarsened later, or not at all. +]] +-------------------------------------------------------------------- diff --git a/graphics/pgf/base/tex/generic/graphdrawing/lua/pgf/gd/force/jedi/forcetypes/ForceAbsoluteValue.lua b/graphics/pgf/base/tex/generic/graphdrawing/lua/pgf/gd/force/jedi/forcetypes/ForceAbsoluteValue.lua new file mode 100644 index 0000000000..4634b70123 --- /dev/null +++ b/graphics/pgf/base/tex/generic/graphdrawing/lua/pgf/gd/force/jedi/forcetypes/ForceAbsoluteValue.lua @@ -0,0 +1,94 @@ +-- Copyright 2014 by Ida Bruhns +-- +-- This file may be distributed and/or modified +-- +-- 1. under the LaTeX Project Public License and/or +-- 2. under the GNU Public License +-- +-- See the file doc/generic/pgf/licenses/LICENSE for more information + +--- This is a subclass of ForceTemplate, which is used to implement forces +-- that work on individual vertices. Forces of this kind simply add an +-- absolute value set in the force data to each vertex' $x$ and $y$ coordinate + +-- Imports +local ForceTemplate = require "pgf.gd.force.jedi.base.ForceTemplate" +local lib = require "pgf.gd.lib" +local Preprocessing = require "pgf.gd.force.jedi.base.Preprocessing" + +-- Localize math functions +local max = math.max +local sqrt = math.sqrt +local min = math.min + +-- Implementation starts here: + +local ForceAbsoluteValue = lib.class { base_class = ForceTemplate } + +function ForceAbsoluteValue:constructor () + ForceTemplate.constructor(self) + self.p = {} +end + + +-- This force class works on a vertex array that is part of the force data +-- defined when adding the force. This array is copied into p. All vertices of +-- the graph are saved in the local variable |ver|. +-- +-- @param v The vertices of the graph we are trying to find a layout for. + +function ForceAbsoluteValue:preprocess(v) + self.ver = v + self.p = self.force.vertices +end + + +-- Applying the force to the vertices and adding the effect to the passed net +-- force array +-- +-- @param data The parameters needed to apply the force: The options table, +-- the current time stamp, an array containing the summed up net +-- forces + +function ForceAbsoluteValue:applyTo(data) + -- locals for speed + local cap = self.force.cap + local value = self.force.value + local net_forces = data.net_forces + local t_max = self.options["maximum time"] + local t_now = data.t_now + local p = self.p + local time_fun = self.force.time_fun + + -- Evaluate time function + local time_factor = time_fun(t_max, t_now) + if time_factor == 0 then + return + end + + for _,v in ipairs(self.ver) do + for _, i in ipairs (self.p) do + -- Is the vertex in the list? + if v.name == i then + + local f = value * time_factor + + -- cap effect if necessary + if cap then + if f <= 0 then + x = max(-cap, f) + else + x = min(cap, f) + end + end + + -- add calculated effect to net forces + local c1 = net_forces[v] + c1.x = c1.x + f + c1.y = c1.y + f + end + end + end +end + +return ForceAbsoluteValue
\ No newline at end of file diff --git a/graphics/pgf/base/tex/generic/graphdrawing/lua/pgf/gd/force/jedi/forcetypes/ForceCanvasDistance.lua b/graphics/pgf/base/tex/generic/graphdrawing/lua/pgf/gd/force/jedi/forcetypes/ForceCanvasDistance.lua new file mode 100644 index 0000000000..8cea1aec6a --- /dev/null +++ b/graphics/pgf/base/tex/generic/graphdrawing/lua/pgf/gd/force/jedi/forcetypes/ForceCanvasDistance.lua @@ -0,0 +1,201 @@ +-- Copyright 2014 by Ida Bruhns +-- +-- This file may be distributed and/or modified +-- +-- 1. under the LaTeX Project Public License and/or +-- 2. under the GNU Public License +-- +-- See the file doc/generic/pgf/licenses/LICENSE for more information + + +--- This is a subclass of ForceTemplate, which is used to implement forces between +-- vertex pairs. The forces depend on the canvas distance of the vertices in +-- the pair. This class is e.~g.~ used for electric forces. + +-- Imports +local ForceTemplate = require "pgf.gd.force.jedi.base.ForceTemplate" +local lib = require "pgf.gd.lib" +local Preprocessing = require "pgf.gd.force.jedi.base.Preprocessing" + +-- Localize math functions +local max = math.max +local sqrt = math.sqrt +local min = math.min + +-- Implementation starts here: +local ForceCanvasDistance = lib.class { base_class = ForceTemplate } + +function ForceCanvasDistance:constructor () + ForceTemplate.constructor(self) + self.p = {} +end + + +-- This force class works on all pairwise disjoint vertex pairs. This +-- function generates a new graph object containing all vertices from the +-- original graph and arcs between all pairwise disjoint vertex pairs. The +-- arcs-table of this new object will be saved in the variable |p|. +-- +-- @param v The vertices of the graph we are trying to find a layout for. + +function ForceCanvasDistance:preprocess(v) + self.p = Preprocessing.allPairs(v) +end + + +-- Applying the force to the vertices and adding the effect to the passed net +-- force array +-- +-- @param data The parameters needed to apply the force: The options table, +-- the current time stamp, an array containing the summed up net +-- forces + +function ForceCanvasDistance:applyTo(data) + -- locals for speed + local cap = self.force.cap + local fun_u = self.force.fun_u + local fun_v = self.force.fun_v + local net_forces = data.net_forces + local t_max = self.options["maximum time"] + local t_now = data.t_now + local k = data.k + local p = self.p + local time_fun = self.force.time_fun + local fw_attributes = self.fw_attributes + + -- Evaluate time function + local time_factor = time_fun(t_max, t_now) + if time_factor == 0 then + return + end + + if not fun_v then + local data = { k = k, attributes = fw_attributes } + for _, i in ipairs(p) do + -- dereference + local p2 = i.head + local p1 = i.tail + local p2_pos = p2.pos + local p1_pos = p1.pos + + -- calculate distance between two points + local x = p2_pos.x - p1_pos.x + local y = p2_pos.y - p1_pos.y + local d = max(sqrt(x*x+y*y),0.1) + + -- apply force function + data.u = p2 + data.v = p1 + data.d = d + local e = fun_u(data) + + -- Include time function + local f = e * time_factor / d + + -- calculate effect on x/y + local g = x * f + local h = y * f + + -- cap effect if necessary + if cap then + if g <= 0 then + x = max(-cap, g) + else + x = min(cap, g) + end + + if h <= 0 then + y = max(-cap, h) + else + y = min(cap, h) + end + else + x = g + y = h + end + + -- add calculated effect to net forces + local c1 = net_forces[p1] + c1.x = c1.x - x + c1.y = c1.y - y + local c2 = net_forces[p2] + c2.x = c2.x + x + c2.y = c2.y + y + end + else + -- There are different functions for head and tail vertex + local data = { k = k, attributes = fw_attributes } + for _, i in ipairs(p) do + -- dereference + local p2 = i.head + local p1 = i.tail + local p2_pos = p2.pos + local p1_pos = p1.pos + + -- calculate distance between two points + local x = p2_pos.x - p1_pos.x + local y = p2_pos.y - p1_pos.y + local d = max(sqrt(x*x+y*y),0.1) + + -- apply force function to distance and k (natural spring length + data.u = p2 + data.v = p1 + data.d = d + local e_head = fun_u(data) + local e_tail = fun_v(data) + + -- Include time function + local f_head = time_factor * e_head / d + local f_tail = time_factor * e_tail / d + + -- calculate effect on x/y + local g_head = x * f_head + local g_tail = x * f_tail + local h_head = y * f_head + local h_tail = y * f_tail + + -- cap effect if necessary + local x_head, x_tail, y_head, y_tail + if cap then + if g_head <= 0 then + x_head = max(-cap, g_head) + else + x_head = min(cap, g_head) + end + + if g_tail <= 0 then + x_tail = max(-cap, g_tail) + else + x_tail = min(cap, g_tail) + end + + if h_head <= 0 then + y_head = max(-cap, h_head) + else + y_head = min(cap, h_head) + end + + if h_tail <= 0 then + y_tail = max(-cap, h_tail) + else + y_tail = min(cap, h_tail) + end + else + x_head = g_head + x_tail = g_tail + y_head = h_head + y_tail = h_tail + end + + -- add calculated effect to net forces + local c1 = net_forces[p1] + c1.x = c1.x - x_tail + c1.y = c1.y - y_tail + local c2 = net_forces[p2] + c2.x = c2.x + x_head + c2.y = c2.y + y_head + end + end +end + +return ForceCanvasDistance
\ No newline at end of file diff --git a/graphics/pgf/base/tex/generic/graphdrawing/lua/pgf/gd/force/jedi/forcetypes/ForceCanvasPosition.lua b/graphics/pgf/base/tex/generic/graphdrawing/lua/pgf/gd/force/jedi/forcetypes/ForceCanvasPosition.lua new file mode 100644 index 0000000000..41edaba358 --- /dev/null +++ b/graphics/pgf/base/tex/generic/graphdrawing/lua/pgf/gd/force/jedi/forcetypes/ForceCanvasPosition.lua @@ -0,0 +1,117 @@ +-- Copyright 2014 by Ida Bruhns +-- +-- This file may be distributed and/or modified +-- +-- 1. under the LaTeX Project Public License and/or +-- 2. under the GNU Public License +-- +-- See the file doc/generic/pgf/licenses/LICENSE for more information + + +--- This is a subclass of ForceTemplate, which is used to implement forces +-- that work on individual vertices. The forces depend on the canvas position +-- of the vertices. This class is e.~g.~ used for gravitational forces. + +local ForceTemplate = require "pgf.gd.force.jedi.base.ForceTemplate" +local lib = require "pgf.gd.lib" + +local ForceCanvasPosition = lib.class { base_class = ForceTemplate } + +-- Localize math functions +local max = math.max +local sqrt = math.sqrt +local min = math.min + +-- Implementation starts here: + +function ForceCanvasPosition:constructor () + ForceTemplate.constructor(self) + self.p = {} +end + + +-- This force class works on individual vertices and only depends on their +-- current position. Thus the vertex table of the current graph is simply +-- copied to the variable |p|. +-- +-- @param v The vertices of the graph we are trying to find a layout for. + +function ForceCanvasPosition:preprocess(v) + self.p = v +end + + +-- Applying the force to the vertices and adding the effect to the passed net +-- force array +-- +-- @param data The parameters needed to apply the force: The options table, +-- the current time stamp, an array containing the summed up net +-- forces + +function ForceCanvasPosition:applyTo(data) + --localize + local cap = self.force.cap + local fun_u = self.force.fun_u + local net_forces = data.net_forces + local t_max = self.options["maximum time"] + local t_now = data.t_now + local p = self.p + local time_fun = self.force.time_fun + local initial_gravity = self.options["gravity"] + local fw_attributes = self.fw_attributes + + -- evaluate time function + local time_factor = time_fun(t_max, t_now) + if time_factor == 0 then + return + end + + -- Find midpoint of all vertices since they will be attracted to this point + local centroid_x, centroid_y = 0,0 + for _, v in ipairs(p) do + local pos = v.pos + centroid_x = centroid_x + pos.x + centroid_y = centroid_y + pos.y + end + centroid_x = centroid_x/#p + centroid_y = centroid_y/#p + + -- Iterate over the precomputed vertex list + for _, v in ipairs(p) do + -- localize + local p1 = v.pos + + -- apply force function + local factor = fun_u{attributes = fw_attributes, u = v} + + -- calculate distance between vertex and centroid + local x = centroid_x - p1.x + local y = centroid_y - p1.y + + -- calculate effect on x/y + local h = factor * time_factor + x = x * h + y = y * h + + -- cap effect if necessary + if cap then + if x <= 0 then + x = max(-cap, x) + else + x = min(cap, x) + end + if y <= 0 then + y = max(-cap, y) + else + y = min(cap, y) + end + end + + -- add calculated effect to net forces + local c = net_forces[v] + c.x = c.x + x + c.y = c.y + y + end +end + +return ForceCanvasPosition
\ No newline at end of file diff --git a/graphics/pgf/base/tex/generic/graphdrawing/lua/pgf/gd/force/jedi/forcetypes/ForceGraphDistance.lua b/graphics/pgf/base/tex/generic/graphdrawing/lua/pgf/gd/force/jedi/forcetypes/ForceGraphDistance.lua new file mode 100644 index 0000000000..86d67f2678 --- /dev/null +++ b/graphics/pgf/base/tex/generic/graphdrawing/lua/pgf/gd/force/jedi/forcetypes/ForceGraphDistance.lua @@ -0,0 +1,205 @@ +-- Copyright 2014 by Ida Bruhns +-- +-- This file may be distributed and/or modified +-- +-- 1. under the LaTeX Project Public License and/or +-- 2. under the GNU Public License +-- +-- See the file doc/generic/pgf/licenses/LICENSE for more information + + +--- This is a subclass of ForceTemplate, which is used to implement forces between +-- vertex pairs. The forces depend on the graph distance of the vertices in +-- the pair. This class is e.\,g.\ used for spring forces. + + +local ForceTemplate = require "pgf.gd.force.jedi.base.ForceTemplate" +local lib = require "pgf.gd.lib" +local Preprocessing = require "pgf.gd.force.jedi.base.Preprocessing" + +-- Localize math functions +local max = math.max +local sqrt = math.sqrt +local min = math.min + +-- Implementation starts here: + +local ForceGraphDistance = lib.class { base_class = ForceTemplate } + +function ForceGraphDistance:constructor () + ForceTemplate.constructor(self) + self.p = {} +end + + +-- This force class works on all pairwise disjoint vertex pairs connected by +-- a path of length maximum $n$. The parameter $n$ is given by the engineer in +-- the force declaration. This function generates a new graph object +-- containing all vertices from the original graph and arcs between all +-- pairwise disjoint vertex pairs. The arcs-table of this new object will be +-- saved in the variable |p|. +-- +-- @param v The vertices of the graph we are trying to find a layout for. + +function ForceGraphDistance:preprocess(v, a) + self.p = Preprocessing.overExactlyNPairs(v, a, self.force.n) +end + + +-- Applying the force to the vertices and adding the effect to the passed net +-- force array +-- +-- @param data The parameters needed to apply the force: The options table, +-- the current time stamp, an array containing the summed up net +-- forces + +function ForceGraphDistance:applyTo(data) + -- locals for speed + local cap = self.force.cap + local fun_u = self.force.fun_u + local fun_v = self.force.fun_v + local net_forces = data.net_forces + local t_max = self.options["maximum time"] + local t_now = data.t_now + local k = data.k + local p = self.p + local time_fun = self.force.time_fun + local fw_attributes = self.fw_attributes + + -- Evaluate time function + local time_factor = time_fun(t_max, t_now) + if time_factor == 0 then + return + end + + if not fun_v then + local data = { k = k, attributes = fw_attributes } + for _, i in ipairs(p) do + -- dereference + local p2 = i.head + local p1 = i.tail + local p2_pos = p2.pos + local p1_pos = p1.pos + + -- calculate distance between two points + local x = p2_pos.x - p1_pos.x + local y = p2_pos.y - p1_pos.y + local d = max(sqrt(x*x+y*y),0.1) + + -- apply force function to distance and k (natural spring length) + data.u = p2 + data.v = p1 + data.d = d + local e = fun_u(data) + + -- Include time function + local f = e * time_factor / d + + -- calculate effect on x/y + local g = x * f + local h = y * f + + -- cap effect if necessary + if cap then + if g <= 0 then + x = max(-cap, g) + else + x = min(cap, g) + end + + if g <= 0 then + y = max(-cap, h) + else + y = min(cap, h) + end + else + x = g + y = h + end + + -- add calculated effect to net forces + local c1 = net_forces[p1] + c1.x = c1.x - x + c1.y = c1.y - y + local c2 = net_forces[p2] + c2.x = c2.x + x + c2.y = c2.y + y + end + else + -- There are different functions for head and tail vertex + local data = { k = k, attributes = fw_attributes } + for _, i in ipairs(p) do + -- dereference + local p2 = i.head + local p1 = i.tail + local p2_pos = p2.pos + local p1_pos = p1.pos + + -- calculate distance between two points + local x = p2_pos.x - p1_pos.x + local y = p2_pos.y - p1_pos.y + + local d = max(sqrt(x*x+y*y),0.1) + + -- apply force function to distance and k (natural spring length + data.u = p2 + data.v = p1 + data.d = d + local e_head = fun_u(data) + local e_tail = fun_v(data) + + -- Include time function + local f_head = time_factor * e_head / d + local f_tail = time_factor * e_tail / d + + -- calculate effect on x/y + local g_head = x * f_head + local g_tail = x * f_tail + local h_head = y * f_head + local h_tail = y * f_tail + + -- cap effect if necessary + local x_head, x_tail, y_head, y_tail + if cap then + if g_head <= 0 then + x_head = max(-cap, g_head) + else + x_head = min(cap, g_head) + end + + if g_tail <= 0 then + x_tail = max(-cap, g_tail) + else + x_tail = min(cap, g_tail) + end + + if h_head <= 0 then + y_head = max(-cap, h_head) + else + y_head = min(cap, h_head) + end + + if h_tail <= 0 then + y_tail = max(-cap, h_tail) + else + y_tail = min(cap, h_tail) + end + else + x_head = g_head + x_tail = g_tail + y_head = h_head + y_tail = h_tail + end + + -- add calculated effect to net forces + local c1 = net_forces[p1] + c1.x = c1.x - x_tail + c1.y = c1.y - y_tail + local c2 = net_forces[p2] + c2.x = c2.x + x_head + c2.y = c2.y + y_head + end + end +end + +return ForceGraphDistance
\ No newline at end of file diff --git a/graphics/pgf/base/tex/generic/graphdrawing/lua/pgf/gd/force/jedi/forcetypes/ForcePullToGrid.lua b/graphics/pgf/base/tex/generic/graphdrawing/lua/pgf/gd/force/jedi/forcetypes/ForcePullToGrid.lua new file mode 100644 index 0000000000..5f53f5da78 --- /dev/null +++ b/graphics/pgf/base/tex/generic/graphdrawing/lua/pgf/gd/force/jedi/forcetypes/ForcePullToGrid.lua @@ -0,0 +1,123 @@ +-- Copyright 2014 by Ida Bruhns +-- +-- This file may be distributed and/or modified +-- +-- 1. under the LaTeX Project Public License and/or +-- 2. under the GNU Public License +-- +-- See the file doc/generic/pgf/licenses/LICENSE for more information + + +--- This is a subclass of ForceTemplate, which is used to implement forces +-- that work on individual vertices and pulls them to a virtual grid with +-- cells of the size determined by the user options |grid x length| and +-- |grid y length|. The forces depend on the canvas position +-- of the vertices relative to th next grid point. This class is e.\,g.\ used +-- for the post-processing technique |snap to grid|. + + +-- Imports +local ForceTemplate = require "pgf.gd.force.jedi.base.ForceTemplate" +local lib = require "pgf.gd.lib" +local Preprocessing = require "pgf.gd.force.jedi.base.Preprocessing" + +-- Localize math functions +local max = math.max +local sqrt = math.sqrt +local min = math.min +local floor = math.floor +local round +function round(number) + return floor((number * 10 + 0.5) / 10) +end + +-- Implementation starts here: + +local ForcePullToGrid = lib.class { base_class = ForceTemplate } + +function ForcePullToGrid:constructor () + ForceTemplate.constructor(self) + self.p = {} +end + +-- This force class works on individual vertices and only depends on their +-- current position. Thus the vertex table of the current graph is simply +-- copied to the variable |p|. +-- +-- @param v The vertices of the graph we are trying to find a layout for. + +function ForcePullToGrid:preprocess(v) + self.p = v +end + + +-- Applying the force to the vertices and adding the effect to the passed net +-- force array +-- +-- @param data The parameters needed to apply the force: The options table, +-- the current time stamp, an array containing the summed up net +-- forces + +function ForcePullToGrid:applyTo(data) + -- locals for speed + local cap = self.force.cap + local net_forces = data.net_forces + local t_max = self.options["maximum time"] + local grid_x_distance = self.options["grid x length"] + local grid_y_distance = self.options["grid y length"] + local t_now = data.t_now + local p = self.p + local time_fun = self.force.time_fun + local length = 5--self.options["node distance"] + + -- Evaluate time function + local time_factor = time_fun(t_max, t_now) + if time_factor == 0 then + return + end + + for _, v in ipairs(p) do + -- dereference + local p1 = v.pos + local p2_x = round(p1.x/grid_x_distance)*grid_x_distance + local p2_y = round(p1.y/grid_y_distance)*grid_y_distance + + -- calculate distance between vertex and grid point + local x = p1.x - p2_x + local y = p1.y - p2_y + local d = max(sqrt(x*x+y*y),0.1) + local l = -d/(length*length) + + -- Include time function + local h = l * time_factor + + -- scale effect according to direction + local f = x * h + local g = y * h + + -- cap effect if necessary + if cap then + if f <= 0 then + x = max(-cap, f) + else + x = min(cap, f) + end + + if g <= 0 then + y = max(-cap, g) + else + y = min(cap, g) + end + else + x = f + y = g + end + + -- add calculated effect to net forces + local c1 = net_forces[v] + c1.x = c1.x - x + c1.y = c1.y - y + end +end + +return ForcePullToGrid
\ No newline at end of file diff --git a/graphics/pgf/base/tex/generic/graphdrawing/lua/pgf/gd/force/jedi/forcetypes/ForcePullToPoint.lua b/graphics/pgf/base/tex/generic/graphdrawing/lua/pgf/gd/force/jedi/forcetypes/ForcePullToPoint.lua new file mode 100644 index 0000000000..985b8eec2e --- /dev/null +++ b/graphics/pgf/base/tex/generic/graphdrawing/lua/pgf/gd/force/jedi/forcetypes/ForcePullToPoint.lua @@ -0,0 +1,119 @@ +-- Copyright 2014 by Ida Bruhns +-- +-- This file may be distributed and/or modified +-- +-- 1. under the LaTeX Project Public License and/or +-- 2. under the GNU Public License +-- +-- See the file doc/generic/pgf/licenses/LICENSE for more information + +--- This is a subclass of ForceTemplate, which is used to implement forces +-- that work on individual vertices and pulls them to a specific point on the +-- canvas. This point is given by the |desired at| option. The forces depend +-- on the canvas position of the vertices relative to the canvas point it is +-- pulled to. + + +-- Imports +local ForceTemplate = require "pgf.gd.force.jedi.base.ForceTemplate" +local lib = require "pgf.gd.lib" +local Preprocessing = require "pgf.gd.force.jedi.base.Preprocessing" + +-- Localize math functions +local max = math.max +local sqrt = math.sqrt +local min = math.min + +-- Implementation starts here: + +local ForcePullToPoint = lib.class { base_class = ForceTemplate } + +function ForcePullToPoint:constructor () + ForceTemplate.constructor(self) + self.p = {} +end + +-- This force class works on individual vertices and depends on their +-- current position as well as the point it is desired at. Thus all vertices +-- where the |desired at| option is set are added to the table |p| together +-- with the point where they are wanted. +-- +-- @param v The vertices of the graph we are trying to find a layout for. + +function ForcePullToPoint:preprocess(v) + for _,vertex in ipairs(v) do + if vertex.options then + local da = vertex.options["desired at"] + if da then + self.p[vertex]= {da} + end + end + end +end + + +-- Applying the force to the vertices and adding the effect to the passed net +-- force array +-- +-- @param data The parameters needed to apply the force: The options table, +-- the current time stamp, an array containing the summed up net +-- forces + +function ForcePullToPoint:applyTo(data) + -- locals for speed + local cap = self.force.cap + local net_forces = data.net_forces + local t_max = self.options["maximum time"] + local t_now = data.t_now + local p = self.p + local time_fun = self.force.time_fun + + -- Evaluate time function + local time_factor = time_fun(t_max, t_now) + if time_factor == 0 then + return + end + + for v, point in pairs(p) do + -- dereference + local p1 = v.pos + local p2 = point[1] + + -- calculate distance between vertex and centroid + local x = p1.x - p2.x + local y = p1.y - p2.y + local d = max(sqrt(x*x+y*y),0.1) + + -- Include time function + local h = d * time_factor + + -- scale effect according to direction + local f = x * h + local g = y * h + + -- cap effect if necessary + if cap then + if f <= 0 then + x = max(-cap, f) + else + x = min(cap, f) + end + + if g <= 0 then + y = max(-cap, g) + else + y = min(cap, g) + end + else + x = f + y = g + end + + -- add calculated effect to net forces + local c1 = net_forces[v] + c1.x = c1.x - x + c1.y = c1.y - y + end +end + +return ForcePullToPoint
\ No newline at end of file diff --git a/graphics/pgf/base/tex/generic/graphdrawing/lua/pgf/gd/force/jedi/initialpositioning/CircularInitialPositioning.lua b/graphics/pgf/base/tex/generic/graphdrawing/lua/pgf/gd/force/jedi/initialpositioning/CircularInitialPositioning.lua new file mode 100644 index 0000000000..ab8f1fad9a --- /dev/null +++ b/graphics/pgf/base/tex/generic/graphdrawing/lua/pgf/gd/force/jedi/initialpositioning/CircularInitialPositioning.lua @@ -0,0 +1,60 @@ +-- Copyright 2014 by Ida Bruhns +-- +-- This file may be distributed and/or modified +-- +-- 1. under the LaTeX Project Public License and/or +-- 2. under the GNU Public License +-- +-- See the file doc/generic/pgf/licenses/LICENSE for more information + +--- This class implements an initial position algorithm for graph drawing, placing the vertices on +-- a circle with th radius given by the |radius| key +local declare = require "pgf.gd.interface.InterfaceToAlgorithms".declare +local InitialTemplate = require "pgf.gd.force.jedi.base.InitialTemplate" +local lib = require "pgf.gd.lib" + +local CircularInitialPositioning = lib.class { base_class = InitialTemplate } + + +--- +declare { + key = "circular initial position", + algorithm = CircularInitialPositioning, + phase = "initial positioning force framework", + phase_default = true +} + +-- Implementation starts here: + +function CircularInitialPositioning:constructor () + InitialTemplate.constructor(self) +end + +function CircularInitialPositioning:run() + -- locals for speed + local vertices = self.vertices + local tmp = (self.options["node pre sep"] + self.options["node post sep"]) + + (self.options["sibling pre sep"] + self.options["sibling post sep"]) + local min_radius = tmp * #self.vertices/2/math.pi + local radius = math.max(self.options.radius, min_radius) + local desired_vertices = self.desired_vertices + -- place vertices where the |desired at | option has been set first + local placed, centroid_x, centroid_y = InitialTemplate:desired(desired_vertices) + local angle = 2*math.pi / #vertices + local a = angle + local sin = math.sin + local cos = math.cos + + for _, vertex in ipairs(vertices) do + -- place all other vertices with respect to the one already placed + if placed[vertex] == nil then + local p = vertex.pos + p.x = sin(a) * radius + centroid_x + p.y = cos(a) * radius + centroid_y + a = a + angle + end + end +end + + +return CircularInitialPositioning
\ No newline at end of file diff --git a/graphics/pgf/base/tex/generic/graphdrawing/lua/pgf/gd/force/jedi/initialpositioning/GridInitialPositioning.lua b/graphics/pgf/base/tex/generic/graphdrawing/lua/pgf/gd/force/jedi/initialpositioning/GridInitialPositioning.lua new file mode 100644 index 0000000000..2b131e8ad9 --- /dev/null +++ b/graphics/pgf/base/tex/generic/graphdrawing/lua/pgf/gd/force/jedi/initialpositioning/GridInitialPositioning.lua @@ -0,0 +1,60 @@ +-- Copyright 2014 by Ida Bruhns +-- +-- This file may be distributed and/or modified +-- +-- 1. under the LaTeX Project Public License and/or +-- 2. under the GNU Public License +-- +-- See the file doc/generic/pgf/licenses/LICENSE for more information + +--- This class implements an initial position algorithm for graph drawing, +-- placing the vertices on a grid with square cells with width |node distance| +local declare = require "pgf.gd.interface.InterfaceToAlgorithms".declare +local InitialTemplate = require "pgf.gd.force.jedi.base.InitialTemplate" +local lib = require "pgf.gd.lib" + +local GridInitialPositioning = lib.class { base_class = InitialTemplate } + + +--- +declare { + key = "grid initial position", + algorithm = GridInitialPositioning, + phase = "initial positioning force framework", +} + +-- Implementation starts here: + +function GridInitialPositioning:constructor () + InitialTemplate.constructor(self) +end + +function GridInitialPositioning:run() + -- locals for speed + local vertices = self.vertices + local dist = self.options["node distance"] + local desired_vertices = self.desired_vertices + -- place vertices where the |desired at | option has been set first + local placed, centroid_x, centroid_y = InitialTemplate:desired(desired_vertices) + local n = math.ceil(math.sqrt(#vertices)) + local x = -dist + local y = 0 + + for i, vertex in ipairs(vertices) do + -- place all other vertices with respect to the one already placed + if placed[vertex] == nil then + if i <= (y/dist+1)*n then + x = x + dist + else + x = 0 + y = y + dist + end + local p = vertex.pos + p.x = x + centroid_x + p.y = y + centroid_y + end + end +end + + +return GridInitialPositioning
\ No newline at end of file diff --git a/graphics/pgf/base/tex/generic/graphdrawing/lua/pgf/gd/force/jedi/initialpositioning/RandomInitialPositioning.lua b/graphics/pgf/base/tex/generic/graphdrawing/lua/pgf/gd/force/jedi/initialpositioning/RandomInitialPositioning.lua new file mode 100644 index 0000000000..ce3a40582c --- /dev/null +++ b/graphics/pgf/base/tex/generic/graphdrawing/lua/pgf/gd/force/jedi/initialpositioning/RandomInitialPositioning.lua @@ -0,0 +1,49 @@ +-- Copyright 2014 by Ida Bruhns +-- +-- This file may be distributed and/or modified +-- +-- 1. under the LaTeX Project Public License and/or +-- 2. under the GNU Public License +-- +-- See the file doc/generic/pgf/licenses/LICENSE for more information + +--- This class implements an initial position algorithm for graph drawing, +-- placing the vertices at random positions. +local declare = require "pgf.gd.interface.InterfaceToAlgorithms".declare +local InitialTemplate = require "pgf.gd.force.jedi.base.InitialTemplate" +local lib = require "pgf.gd.lib" + +local RandomInitialPositioning = lib.class { base_class = InitialTemplate } + +--- +declare { + key = "random initial position", + algorithm = RandomInitialPositioning, + phase = "initial positioning force framework" +} + +-- Implementation starts here: + +function RandomInitialPositioning:constructor () + InitialTemplate.constructor(self) +end + +function RandomInitialPositioning:run() + -- locals for speed + local random = lib.random + local vertices = self.vertices + local desired_vertices = self.desired_vertices + -- place vertices where the |desired at | option has been set first + local placed, centroid_x, centroid_y = InitialTemplate:desired(desired_vertices) + + for _, vertex in ipairs(vertices) do + -- place all other vertices with respect to the one already placed + if placed[vertex] == nil then + p = vertex.pos + p.x = 100 * random() + centroid_x + p.y = 100 * random() + centroid_y + end + end +end + +return RandomInitialPositioning diff --git a/graphics/pgf/base/tex/generic/graphdrawing/lua/pgf/gd/force/jedi/library.lua b/graphics/pgf/base/tex/generic/graphdrawing/lua/pgf/gd/force/jedi/library.lua new file mode 100644 index 0000000000..5d5554ce35 --- /dev/null +++ b/graphics/pgf/base/tex/generic/graphdrawing/lua/pgf/gd/force/jedi/library.lua @@ -0,0 +1,115 @@ +-- Copyright 2014 by Ida Bruhns +-- +-- This file may be distributed and/or modified +-- +-- 1. under the LaTeX Project Public License and/or +-- 2. under the GNU Public License +-- +-- See the file doc/generic/pgf/licenses/LICENSE for more information + + +--- The library providing the graph drawing framework Jedi +-- This library requires all graph drawing algorithms and initial +-- positioning algorithms provided with the first release of Jedi. +-- It also defines the mass key attached to all vertices. + +-- Library name +local jedi + +-- require initial positioning algorithms +require "pgf.gd.force.jedi.initialpositioning.CircularInitialPositioning" +require "pgf.gd.force.jedi.initialpositioning.RandomInitialPositioning" +require "pgf.gd.force.jedi.initialpositioning.GridInitialPositioning" + +-- require graph drawing algorithms +require "pgf.gd.force.jedi.algorithms.FruchtermanReingold" +require "pgf.gd.force.jedi.algorithms.HuSpringElectricalFW" +require "pgf.gd.force.jedi.algorithms.SimpleSpring" +require "pgf.gd.force.jedi.algorithms.SocialGravityCloseness" +require "pgf.gd.force.jedi.algorithms.SocialGravityDegree" + + +-- define parameter +local declare = require "pgf.gd.interface.InterfaceToAlgorithms".declare + +--- +declare { + key = "maximum displacement per step", + type = "length", + initial = "100", + documentation_in = "pgf.gd.force.jedi.doc" +} + +--- +declare { + key = "global speed factor", + type = "length", + initial = "1", + documentation_in = "pgf.gd.force.jedi.doc" +} + +--- +declare { + key = "maximum time", + type = "number", + initial = "50", + documentation_in = "pgf.gd.force.jedi.doc" +} + +--- +declare { + key = "find equilibrium", + type = "boolean", + initial = true, + documentation_in = "pgf.gd.force.jedi.doc" +} + +--- +declare { + key = "equilibrium threshold", + type = "number", + initial = "3", + documentation_in = "pgf.gd.force.jedi.doc" +} + +--- +declare { + key = "grid x length", + type = "length", + initial = "10pt", + documentation_in = "pgf.gd.force.jedi.doc" +} + +--- +declare { + key = "grid y length", + type = "length", + initial = "10pt", + documentation_in = "pgf.gd.force.jedi.doc" +} + +--- +declare { + key = "snap to grid", + type = "boolean", + initial = false, + documentation_in = "pgf.gd.force.jedi.doc" +} + +--- +declare { + key = "mass", + type = "number", + initial = "1", + + documentation_in = "pgf.gd.force.jedi.doc" +} + +--- +declare { + key = "coarsening weight", + type = "number", + initial = "1", + + documentation_in = "pgf.gd.force.jedi.doc" +} diff --git a/graphics/pgf/base/tex/generic/graphdrawing/lua/pgf/gd/force/library.lua b/graphics/pgf/base/tex/generic/graphdrawing/lua/pgf/gd/force/library.lua new file mode 100644 index 0000000000..acfda34ffc --- /dev/null +++ b/graphics/pgf/base/tex/generic/graphdrawing/lua/pgf/gd/force/library.lua @@ -0,0 +1,126 @@ +-- Copyright 2012 by Till Tantau +-- +-- This file may be distributed an/or modified +-- +-- 1. under the LaTeX Project Public License and/or +-- 2. under the GNU Public License +-- +-- See the file doc/generic/pgf/licenses/LICENSE for more information + +-- @release $Header$ + + + +--- +-- Nature creates beautiful graph layouts all the time. Consider a +-- spider's web: Nodes are connected by edges in a visually most pleasing +-- manner (if you ignore the spider in the middle). The layout of a +-- spider's web is created just by the physical forces exerted by the +-- threads. The idea behind force-based graph drawing algorithms is to +-- mimic nature: We treat edges as threads that exert forces and simulate +-- into which configuration the whole graph is ``pulled'' by these +-- forces. +-- +-- When you start thinking about for a moment, it turns out that there +-- are endless variations of the force model. All of these models have +-- the following in common, however: +-- % +-- \begin{itemize} +-- \item ``Forces'' pull and push at the nodes in different directions. +-- \item The effect of these forces is simulated by iteratively moving +-- all the nodes simultaneously a little in the direction of the forces +-- and by then recalculating the forces. +-- \item The iteration is stopped either after a certain number of +-- iterations or when a \emph{global energy minimum} is reached (a very +-- scientific way of saying that nothing happens anymore). +-- \end{itemize} +-- +-- The main difference between the different force-based approaches is +-- how the forces are determined. Here are some ideas what could cause a +-- force to be exerted between two nodes (and there are more): +-- % +-- \begin{itemize} +-- \item If the nodes are connected by an edge, one can treat the edge as +-- a ``spring'' that has a ``natural spring dimension''. If the nodes +-- are nearer than the spring dimension, they are push apart; if they +-- are farther aways than the spring dimension, they are pulled together. +-- \item If two nodes are connected by a path of a certain length, the +-- nodes may ``wish to be at a distance proportional to the path +-- length''. If they are nearer, they are pushed apart; if they are +-- farther, they are pulled together. (This is obviously a +-- generalization of the previous idea.) +-- \item There may be a general force field that pushes nodes apart (an +-- electrical field), so that nodes do not tend to ``cluster''. +-- \item There may be a general force field that pulls nodes together (a +-- gravitational field), so that nodes are not too loosely scattered. +-- \item There may be highly nonlinear forces depending on the distance of +-- nodes, so that nodes very near to each get pushed apart strongly, +-- but the effect wears of rapidly at a distance. (Such forces are +-- known as strong nuclear forces.) +-- \item There rotational forces caused by the angles between the edges +-- leaving a node. Such forces try to create a \emph{perfect angular +-- resolution} (a very scientific way of saying that all angles +-- at a node are equal). +-- \end{itemize} +-- +-- Force-based algorithms combine one or more of the above ideas into a +-- single algorithm that uses ``good'' formulas for computing the +-- forces. +-- +-- Currently, three algorithms are implemented in this library, two of +-- which are from the first of the following paper, while the third is +-- from the third paper: +-- % +-- \begin{itemize} +-- \item +-- Y. Hu. +-- \newblock Efficient, high-quality force-directed graph drawing. +-- \newblock \emph{The Mathematica Journal}, 2006. +-- \item +-- C. Walshaw. +-- \newblock A multilevel algorithm for force-directed graph +-- drawing. +-- \newblock In J. Marks, editor, \emph{Graph Drawing}, Lecture Notes in +-- Computer Science, 1984:31--55, 2001. +-- \end{itemize} +-- +-- Our implementation is described in detail in the following +-- diploma thesis: +-- % +-- \begin{itemize} +-- \item +-- Jannis Pohlmann, +-- \newblock \emph{Configurable Graph Drawing Algorithms +-- for the \tikzname\ Graphics Description Language,} +-- \newblock Diploma Thesis, +-- \newblock Institute of Theoretical Computer Science, Universit\"at +-- zu L\"ubeck, 2011.\\[.5em] +-- \newblock Online at +-- \url{http://www.tcs.uni-luebeck.de/downloads/papers/2011/}\\ \url{2011-configurable-graph-drawing-algorithms-jannis-pohlmann.pdf} +-- \end{itemize} +-- +-- In the future, I hope that most, if not all, of the force-based +-- algorithms become ``just configuration options'' of a general +-- force-based algorithm similar to the way the modular Sugiyama method +-- is implemented in the |layered| graph drawing library. +-- +-- @library + +local force -- Library name + +-- Load declarations from: +require "pgf.gd.force.ControlDeclare" +require "pgf.gd.force.ControlStart" +require "pgf.gd.force.ControlIteration" +require "pgf.gd.force.ControlSprings" +require "pgf.gd.force.ControlElectric" +require "pgf.gd.force.ControlCoarsening" + +require "pgf.gd.force.SpringLayouts" +require "pgf.gd.force.SpringElectricalLayouts" + +-- Load algorithms from: +require "pgf.gd.force.SpringHu2006" +require "pgf.gd.force.SpringElectricalHu2006" +require "pgf.gd.force.SpringElectricalWalshaw2000" + diff --git a/graphics/pgf/base/tex/generic/graphdrawing/lua/pgf/gd/interface.lua b/graphics/pgf/base/tex/generic/graphdrawing/lua/pgf/gd/interface.lua new file mode 100644 index 0000000000..df2649521b --- /dev/null +++ b/graphics/pgf/base/tex/generic/graphdrawing/lua/pgf/gd/interface.lua @@ -0,0 +1,26 @@ +-- Copyright 2012 by Till Tantau +-- +-- This file may be distributed an/or modified +-- +-- 1. under the LaTeX Project Public License and/or +-- 2. under the GNU Public License +-- +-- See the file doc/generic/pgf/licenses/LICENSE for more information + +--- @release $Header$ + + + +-- Imports + +require "pgf" +require "pgf.gd" + + +-- Declare namespace +pgf.gd.interface = {} + + +-- Done + +return pgf.gd.interface
\ No newline at end of file diff --git a/graphics/pgf/base/tex/generic/graphdrawing/lua/pgf/gd/interface/InterfaceCore.lua b/graphics/pgf/base/tex/generic/graphdrawing/lua/pgf/gd/interface/InterfaceCore.lua new file mode 100644 index 0000000000..8473eb3f10 --- /dev/null +++ b/graphics/pgf/base/tex/generic/graphdrawing/lua/pgf/gd/interface/InterfaceCore.lua @@ -0,0 +1,191 @@ +-- Copyright 2012 by Till Tantau +-- +-- This file may be distributed an/or modified +-- +-- 1. under the LaTeX Project Public License and/or +-- 2. under the GNU Public License +-- +-- See the file doc/generic/pgf/licenses/LICENSE for more information + +-- @release $Header$ + + + +--- +-- 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. + +local InterfaceCore = { + -- The main binding. Set by |InterfaceToDisplay.bind| method. + binding = nil, + + -- The stack of Scope objects. + scopes = {}, + + -- The collection kinds. + collection_kinds = {}, + + -- The algorithm classes + algorithm_classes = {}, + + -- The declared keys + keys = {}, + + -- The phase kinds + phase_kinds = {}, + + -- Internals for handling the options stack + option_stack = {}, + option_cache_height = nil, + option_initial = { + algorithm_phases = { + ["preprocessing stack"] = {}, + ["edge routing stack"] = {}, + ["postprocessing stack"] = {}, + } + }, + option_aliases = { + [{}] = true -- Remove, once Lua Link Bug is fixed + }, + + -- Constant strings for special collection kinds. + sublayout_kind = "INTERNAL_sublayout_kind", + subgraph_node_kind = "INTERNAL_subgraph_node_kind", +} + +-- Namespace +require("pgf.gd.interface").InterfaceCore = InterfaceCore + + +InterfaceCore.option_initial.__index = InterfaceCore.option_initial +InterfaceCore.option_initial.algorithm_phases.__index = InterfaceCore.option_initial.algorithm_phases + + +-- Imports +local Coordinate = require "pgf.gd.model.Coordinate" + + +--- Returns the top scope +-- +-- @return The current top scope, which is the scope in which +-- everything should happen right now. + +function InterfaceCore.topScope() + return assert(InterfaceCore.scopes[#InterfaceCore.scopes], "no graph drawing scope open") +end + + + +local factors = { + cm=28.45274, + mm=2.84526, + pt=1.0, + bp=1.00374, + sp=0.00002, + pc=12.0, + em=10, + ex=4.30554, + ["in"]=72.27, + dd=1.07, + cc=12.8401, + [""]=1, +} + +local time_factors = { + s=1, + ms=0.001, + min=60, + h=3600 +} + +local directions = { + down = -90, + up = 90, + left = 180, + right = 0, + south = -90, + north = 90, + west = 180, + east = 0, + ["north east"] = 45, + ["north west"] = 135, + ["south east"] = -45, + ["south west"] = -135, + ["-"] = 0, + ["|"] = -90, +} + +--- +-- 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. +-- +-- @param s A parameter value or a string. +-- @param t The type of the parameter +-- +-- @return If |s| is not a string, it is just returned. If it is a +-- string, it is converted to the type |t|. + +function InterfaceCore.convert(s,t) + if type(s) ~= "string" then + return s + elseif t == "number" then + return tonumber(s) + elseif t == "length" then + local num, dim = string.match(s, "([%d.]+)(.*)") + return tonumber(num) * assert(factors[dim], "unknown unit") + elseif t == "time" then + local num, dim = string.match(s, "([%d.]+)(.*)") + return tonumber(num) * assert(time_factors[dim], "unknown time unit") + elseif t == "string" then + return s + elseif t == "canvas coordinate" or t == "coordinate" then + local x, y = string.match(s,"%(([%d.]+)pt,([%d.]+)pt%)") + return Coordinate.new(tonumber(x),tonumber(y)) + elseif t == "boolean" then + return s == "true" + elseif t == "raw" then + return loadstring(s)() + elseif t == "direction" then + return directions[s] or tonumber(s) + elseif t == "nil" or t == nil then + return nil + else + error ("unknown parameter type") + end +end + + +-- Done + +return InterfaceCore diff --git a/graphics/pgf/base/tex/generic/graphdrawing/lua/pgf/gd/interface/InterfaceToAlgorithms.lua b/graphics/pgf/base/tex/generic/graphdrawing/lua/pgf/gd/interface/InterfaceToAlgorithms.lua new file mode 100644 index 0000000000..1187fbc313 --- /dev/null +++ b/graphics/pgf/base/tex/generic/graphdrawing/lua/pgf/gd/interface/InterfaceToAlgorithms.lua @@ -0,0 +1,968 @@ +-- Copyright 2012 by Till Tantau +-- +-- This file may be distributed an/or modified +-- +-- 1. under the LaTeX Project Public License and/or +-- 2. under the GNU Public License +-- +-- See the file doc/generic/pgf/licenses/LICENSE for more information + +-- @release $Header$ + + + +--- +-- This class provides the interface between the graph drawing system +-- and algorithms. Another class, |InterfaceToDisplay|, binds the +-- display layers (like \tikzname\ or a graph drawing editor) to the +-- graph drawing system ``from the other side''. +-- +-- The functions declared here can be used by algorithms to +-- communicate with the graph drawing system, which will usually +-- forward the ``requests'' of the algorithms to the display layers in +-- some way. For instance, when you declare a new parameter, this +-- parameter will become available on the display layer. + +local InterfaceToAlgorithms = {} + +-- Namespace +require("pgf.gd.interface").InterfaceToAlgorithms = InterfaceToAlgorithms + + +-- Imports +local InterfaceCore = require "pgf.gd.interface.InterfaceCore" +local InterfaceToDisplay = require "pgf.gd.interface.InterfaceToDisplay" +local InterfaceToC = require "pgf.gd.interface.InterfaceToC" + +local LookupTable = require "pgf.gd.lib.LookupTable" +local LayoutPipeline = require "pgf.gd.control.LayoutPipeline" + +local Edge = require "pgf.gd.model.Edge" + +local lib = require "pgf.gd.lib" + +local doc = require "pgf.gd.doc" + +-- Forwards + +local declare_handlers + + + + +--- +-- Adds a handler for the |declare| function. The |declare| +-- command is just a ``dispatcher'' to one of many possible +-- declaration functions. Which function is used, depends on which +-- fields are present in the table passed to |declare|. For each +-- registered handler, we call the |test| function. If it returns +-- neither |nil| nor |false|, the |handler| field of this handler is +-- called. If it returns |true|, the handler immediately +-- finishes. Otherwise, the next handler is tried. + +function InterfaceToAlgorithms.addHandler(test, handler) + table.insert(declare_handlers, 1, { test = test, handler = handler }) +end + + + +-- Local stuff + +local key_metatable = {} + +--- +-- This function is the ``work-horse'' for declaring things. It allows +-- you to specify on the algorithmic layer that a key ``is available'' +-- for use on the display layer. There is just one function for +-- handling all declarations in order to make the declarations +-- easy-to-use since you just need to import a single function: +-- % +--\begin{codeexample}[code only, tikz syntax=false] +--local declare = require "pgf.gd.interface.InterfaceToAlgorithms".declare +--\end{codeexample} +-- +-- You can now use |declare| it as follows: You pass it a table +-- containing information about the to-be-declared key. The table +-- \emph{must} have a field |key| whose value is unique and must be a +-- string. If the value of |key| is, say, |"foo"|, the +-- parameter can be set on the display layer such as, say, the +-- \tikzname\ layer, using |/graph drawing/foo|. Here is a typical +-- example of how a declaration is done: +-- % +--\begin{codeexample}[code only, tikz syntax=false] +-- --- +-- declare { +-- key = "electrical charge", +-- type = "number", +-- initial = "1.0", +-- +-- summary = "The ``electrical charge'' is a property...", +-- documentation = [[...]], +-- examples = [[...]] +-- } +--\end{codeexample} +-- +-- \medskip\noindent\textbf{Inlining Documentation.} +-- The three keys |summary|, |documentation| and |examples| are +-- intended for the display layer to give the users information about +-- what the key does. The |summary| should be a string that succinctly +-- describes the option. This text will typically be displayed for +-- instance as a ``tool tip'' or in an option overview. The +-- |documentation| optionally provides more information and should be +-- typeset using \TeX. The |examples| can either be a single string or +-- an array of strings. Each should be a \tikzname\ example +-- demonstrating how the key is used. +-- +-- Note that you can take advantage of the Lua syntax of enclosing +-- very long multi-line strings in |[[| and |]]|. As a bonus, if the +-- summary, documentation, or an example starts and ends with a quote, +-- these two quotes will be stripped. This allows you to enclose the +-- whole multi-line string (additionally) in quotes, leading to better +-- syntax highlighting in editors. +-- +-- \medskip\noindent\textbf{External Documentation.} +-- It is sometimes more desirable to put the documentation of a key +-- into an external file. First, this makes the code leaner and, thus, +-- faster to read (both for humans and for computers). Second, for C +-- code, it is quite inconvenient to have long strings inside a C +-- file. In such cases, you can use the |documentation_in| field: +-- % +--\begin{codeexample}[code only, tikz syntax=false] +-- --- +-- declare { +-- key = "electrical charge", +-- type = "number", +-- initial = "1.0", +-- documentation_in = "some_filename" +-- } +--\end{codeexample} +-- +-- The |some_filename| must be the name of a Lua file that will be +-- read ``on demand'', that is, whenever someone tries to access the +-- documentation, summary, or examples field of the key, this file +-- will be loaded using |require|. The file should then use +-- |pgf.gd.doc| to install the missing information in the keys. +-- +-- \medskip\noindent\textbf{The Use Field.} +-- When you declare a key, you can provide a |use| field. If present, +-- you must set it to an array of small tables which have two fields: +-- % +-- \begin{itemize} +-- \item |key| This is the name of another key or a function. +-- \item |value| This is either a value (like a string or a number) or +-- a function or |nil|. +-- \end{itemize} +-- +-- Here is an example: +-- % +--\begin{codeexample}[code only, tikz syntax=false] +-- --- +-- declare { +-- key = "binary tree layout", +-- use = { +-- { key = "minimum number of children", value = 2 }, +-- { key = "significant sep", value = 12 }, +-- { key = "tree layout" } +-- }, +-- summary = "The |binary tree layout| places node...", +-- documentation = ..., +-- examples = ..., +-- } +--\end{codeexample} +-- +-- The effect of a |use| field is the following: Whenever the key is +-- encountered on the option stack, the key is first handled +-- normally. Then, we iterate over all elements of the |use| +-- array. For each element, we perform the action as if the |key| of +-- the array had been set explicitly to the value given by the |value| +-- field. If the |value| is a function, we pass a different value to +-- the key, namely the result of applying the function to the value +-- originally passed to the original key. Here is a typical example: +-- % +--\begin{codeexample}[code only, tikz syntax=false] +-- --- +-- declare { +-- key = "level sep", +-- type = "length", +-- use = { +-- { key = "level pre sep", value = function (v) return v/2 end }, +-- { key = "level post sep", value = function (v) return v/2 end } +-- }, +-- summary = "..." +-- } +--\end{codeexample} +-- +-- Just like the value, the key itself can also be a function. In this +-- case, the to-be-used key is also computed by applying the function +-- to the value passed to the original key. +-- +-- As mentioned at the beginning, |declare| is a work-horse that will call +-- different internal functions depending on whether you declare a +-- parameter key or a new algorithm or a collection kind. Which kind +-- of declaration is being done is detected by the presence of certain +-- fields in the table passed to |t|. The different kind of +-- possible declarations are documented in the |declare_...| +-- functions. Note that these functions are internal and cannot be +-- called from outside; you must use the |declare| function. +-- +-- @param t A table contain the field |key| and other fields as +-- described. + +function InterfaceToAlgorithms.declare (t) + local keys = InterfaceCore.keys + + -- Sanity check: + assert (type(t.key) == "string" and t.key ~= "", "parameter key may not be the empty string") + if keys[t.key] or t.keys == "algorithm_phases" then + error("parameter '" .. t.key .. "' already declared") + end + + for _,h in ipairs (declare_handlers) do + if h.test(t) then + if h.handler(t) then + break + end + end + end + + -- Attach metatable: + setmetatable (t, key_metatable) + + -- Set! + keys[t.key] = t + keys[#keys + 1] = t +end + + +function key_metatable.__index (key_table, what) + if what == "documentation" or what == "summary" or what == "examples" then + local doc = rawget(key_table,"documentation_in") + if doc then + require (doc) + return rawget(key_table, what) + end + end +end + + + +--- +-- This function is called by |declare| for ``normal parameter keys'', +-- which are all keys for which no special field like |algorithm| or +-- |layer| is declared. You write +-- % +--\begin{codeexample}[code only, tikz syntax=false] +-- --- +-- declare { +-- key = "electrical charge", +-- type = "number", +-- initial = "1.0", +-- +-- summary = "The ``electrical charge'' is a property...", +-- documentation = [[...]], +-- examples = [[...]] +-- } +--\end{codeexample} +-- +-- When an author writes |my node[electrical charge=5-3]| in the +-- description of her graph, the object |vertex| corresponding to the +-- node |my node| will have a field |options| attached to it with +-- % +--\begin{codeexample}[code only, tikz syntax=false] +--vertex.options["electrical charge"] == 2 +--\end{codeexample} +-- +-- The |type| field does not refer to Lua types. Rather, these types are +-- sensible types for graph drawing and they are mapped by the higher +-- layers to Lua types. In detail, the following types are available: +-- % +-- \begin{itemize} +-- \item |number| A dimensionless number. Will be mapped to a normal +-- Lua |number|. So, when the author writes |foo=5*2|, the |foo| key +-- of the |options| field of the corresponding object will be set to +-- |10.0|. +-- \item |length| A ``dimension'' in the sense of \TeX\ (a number with +-- a dimension like |cm| attached to it). It is the job of the display +-- layer to map this to a number in ``\TeX\ points'', that is, to a +-- multiple of $1/72.27$th of an inch. +-- \item |time| A ``time'' in the sense of |\pgfparsetime|. Examples +-- are |6s| or |0.1min| or |6000ms|, all of which will map to |6|. +-- \item |string| Some text. Will be mapped to a Lua |string|. +-- \item |canvas coordinate| A position on the canvas. Will be mapped +-- to a |model.Coordinate|. +-- \item |boolean| A Boolean value. +-- \item |raw| Some to-be-executed Lua text. +-- \item |direction| Normally, an angle; however, +-- the special values of |down|, |up|, |left|, |right| as well as the +-- directions |north|, |north west|, and so on are also legal on the +-- display layer. All of them will be mapped to a number. Furthermore, +-- a vertical bar (\verb!|!) will be mapped to |-90| and a minus sign +-- (|-|) will be mapped to |0|. +-- \item |hidden| A key of this type ``cannot be set'', that is, +-- users cannot set this key at all. However algorithms can still read +-- this key and, through the use of |alias|, can use the key as a +-- handle to another key. +-- \item |user value| The key stores a Lua user value (userdata). Such +-- keys can only be set from C since user values cannot be created in +-- Lua (let alone in \tikzname). +-- \end{itemize} +-- +-- If the |type| field is missing, it is automatically set to +-- |"string"|. +-- +-- A parameter can have an |initial| value. This value will be used +-- whenever the parameter has not been set explicitly for an object. +-- +-- A parameter can have a |default| value. This value will be used as +-- the parameter value whenever the parameter is explicitly set, but +-- no value is provided. For a key of type |"boolean"|, if no +-- |default| is provided, |"true"| will be used automatically. +-- +-- A parameter can have an |alias| field. This field must be set to +-- the name of another key or to a function. Whenever you access the +-- current key and this key is not set, the |alias| key is tried +-- instead. If it is set, its value will be returned (if the |alias| +-- key has itself an alias set, this is tried recursively). If the +-- alias is not set either and neither does it have an initial value, +-- the |initial| value is used. Note that in case the alias has its +-- |initial| field set, the |initial| value of the current key will +-- never be used. +-- +-- The main purpose of the current key is to allow algorithms to +-- introduce their own terminology for keys while still having access +-- to the standard keys. For instance, the |OptimalHierarchyLayout| +-- class uses the name |layerDistance| for what would be called +-- |level distance| in the rest of the graph drawing system. In this +-- case, we can declare the |layerDistance| key as follows: +-- % +--\begin{codeexample}[code only, tikz syntax=false] +-- declare { +-- key = "layerDistance", +-- type = "length", +-- alias = "level distance" +-- } +--\end{codeexample} +-- +-- Inside the algorithm, we can write |...options.layerDistance| and +-- will get the current value of the |level distance| unless the +-- |layerDistance| has been set explicitly. Indeed, we might set the +-- |type| to |hidden| to ensure that \emph{only} the |level distance| +-- can and must set to set the layerDistance. +-- +-- Note that there is a difference between |alias| and the |use| +-- field: Suppose we write +-- % +--\begin{codeexample}[code only, tikz syntax=false] +-- declare { +-- key = "layerDistance", +-- type = "length", +-- use = { +-- { key = "level distance", value = lib.id } +-- } +-- } +--\end{codeexample} +-- +-- Here, when you say |layerDistance=1cm|, the |level distance| itself +-- will be modified. When the |level distance| is set, however, the +-- |layerDistance| will not be modified. +-- +-- If the alias is a function, it will be called with the option table +-- as its parameter. You can thus say things like +-- % +--\begin{codeexample}[code only, tikz syntax=false] +-- declare { +-- key = "layerDistance", +-- type = "length", +-- alias = function (option) +-- return option["layer pre dist"] + option["layer post dist"] +-- end +-- } +--\end{codeexample} +-- +-- As a special courtesy to C code, you can also set the key +-- |alias_function_string|, which allows you to put the function into +-- a string that is read using |loadstring|. +-- +-- (You cannot call this function directly, it is included for +-- documentation purposes only.) +-- +-- @param t The table originally passed to |declare|. + +local function declare_parameter (t) + + t.type = t.type or "string" + + if t.type == "boolean" and t.default == nil then + t.default = true + end + + -- Normal key + assert (type(t.type) == "string", "key type must be a string") + + -- Declare via the hub: + if t.type ~= "hidden" then + InterfaceCore.binding:declareCallback(t) + + -- Handle initials: + if t.initial then + InterfaceCore.option_initial[t.key] = InterfaceCore.convert(t.initial, t.type) + end + end + + if t.alias_function_string and not t.alias then + local count = 0 + t.alias = load ( + function () + count = count + 1 + if count == 1 then + return "return " + elseif count == 2 then + return t.alias_function_string + else + return nil + end + end)() + end + + if t.alias then + assert (type(t.alias) == "string" or type(t.alias == "function"), "alias must be a string or a function") + InterfaceCore.option_aliases[t.key] = t.alias + end + + return true +end + + + + +--- +-- This function is called by |declare| for ``algorithm +-- keys''. These keys are normally used without a value as in just +-- |\graph[tree layout]|, but you can optionally pass a value to +-- them. In this case, this value must be the name of a \emph{phase} +-- and the algorithm of this phase will be set (and not the +-- default phase of the key), see the description of phases below for +-- details. +-- +-- Algorithm keys are detected by the presence of the field |algorithm| +-- in the table |t| passed to |declare|. Here is an example of how it +-- is used: +-- % +--\begin{codeexample}[code only, tikz syntax=false] +-- local ReingoldTilford1981 = {} +-- +-- --- +-- declare { +-- key = "tree layout", +-- algorithm = ReingoldTilford1981, +-- +-- preconditions = { +-- connected = true, +-- tree = true +-- }, +-- +-- postconditions = { +-- upward_oriented = true +-- }, +-- +-- summary = "The Reingold--Tilford method is...", +-- documentation = ..., +-- examples = ..., +-- } +-- +-- function ReingoldTilford1981:run() +-- ... +-- end +--\end{codeexample} +-- +-- The |algorithm| field expects either a table or a string as +-- value. If you provide a string, then |require| will be applied to +-- this string to obtain the table; however, this will happen only +-- when the key is actually used for the first time. This means that +-- you can declare (numerous) algorithms in a library without these +-- algorithms actually being loaded until they are needed. +-- +-- Independently of how the table is obtained, it will be ``upgraded'' +-- to a class by setting its |__index| field and installing a static +-- |new| function (which takes a table of initial values as +-- argument). Both these settings will only be done if they have not +-- yet been performed. +-- +-- Next, you can specify the fields |preconditions| and +-- |postconditions|. The preconditions are a table that tell the graph +-- drawing engine what kind of graphs your algorithm expects. If the +-- input graph is not of this kind, it will be automatically +-- transformed to meet this condition. Similarly, the postconditions +-- tell the engine about properties of your graph after the algorithm +-- has run. Again, additional transformations may be performed. +-- +-- You can also specify the field |phase|. This tells the graph +-- drawing engine which ``phase'' of the graph drawing process your +-- option applies to. Each time you select an algorithm later on +-- through use of the algorithm's key, the algorithm for this phase +-- will be set; algorithms of other phases will not be changed. +-- For instance, when an algorithm is part of the spanning tree +-- computation, its phase will be |"spanning tree computation"| and +-- using its key does not change the main algorithm, but only the +-- algorithm used during the computation of a spanning tree for the +-- current graph (in case this is needed by the main algorithm). In +-- case the |phase| field is missing, the phase |main| is used. Thus, +-- when no phase field is given, the key will change the main +-- algorithm used to draw the graph. +-- +-- Later on, the algorithm set for the current phase can be accessed +-- through the special |algorithm_phases| field of |options| +-- tables. The |algorithm_phases| table will contain two fields for each +-- phase for which some algorithm has been set: One field is the name +-- of the phase and its value will be the most recently set algorithm +-- (class) set for this phase. The other field is the name of the +-- phase followed by |" stack"|. It will contain an array of all +-- algorithm classes that have been set for this key with the most +-- recently at the end. +-- +-- The following example shows the declaration of an algorithm that is +-- the default for the phase |"spanning tree computation"|: +-- % +--\begin{codeexample}[code only, tikz syntax=false] +-- --- +-- declare { +-- key = "breadth first spanning tree", +-- algorithm = { +-- run = +-- function (self) +-- return SpanningTreeComputation.computeSpanningTree(self.ugraph, false, self.events) +-- end +-- }, +-- phase = "spanning tree computation", +-- phase_default = true, +-- summary = ... +-- } +--\end{codeexample} +-- +-- The algorithm is called as follows during a run of the main +-- algorithms: +-- % +--\begin{codeexample}[code only, tikz syntax=false] +-- local graph = ... -- the graph object +-- local spanning_algorithm_class = graph.options.algorithm_phases["spanning tree computation"] +-- local spanning_algorithm = +-- spanning_algorithm_class.new{ +-- ugraph = ugraph, +-- events = scope.events +-- } +-- local spanning_tree = spanning_algorithm:run() +--\end{codeexample} +-- +-- If you set the |phase_default| field of |t| to |true|, the algorithm will +-- be installed as the default algorithm for the phase. This can be +-- done only once per phase. Furthermore, for such a default algorithm +-- the |algorithm| key must be table, it may not be a string (in other +-- words, all default algorithms are loaded immediately). Accessing +-- the |algorithm_phases| table for a phase for which no algorithm has +-- been set will result in the default algorithm and the phase stack +-- will also contain this algorithm; otherwise the phase stack will be empty. +-- +-- (You cannot call this function directly, it is included for +-- documentation purposes only.) +-- +-- @param t The table originally passed to |declare|. + +local function declare_algorithm (t) + -- Algorithm declaration! + assert(type(t.algorithm) == "table" or type(t.algorithm) == "string") + + t.phase = t.phase or "main" + + local function make_class () + local class + + if type(t.algorithm) == "table" then + class = lib.class(t.algorithm) + else + class = lib.class(require(t.algorithm)) + end + + -- Now, save pre- and postconditions + class.preconditions = t.preconditions or {} + class.postconditions = t.postconditions or {} + + -- Save phase + class.phase = t.phase + + -- Compatibility + class.old_graph_model = t.old_graph_model + + return class + end + + -- Store this: + local store_me + if type(t.algorithm) == "table" then + store_me = make_class() + else + store_me = make_class + end + + -- Save in the algorithm_classes table: + InterfaceCore.algorithm_classes[t.key] = store_me + + assert(t.type == nil, "type may not be set for an algorithm key") + t.type = "string" + + -- Install! + InterfaceCore.binding:declareCallback(t) + + if t.phase_default then + assert (not InterfaceCore.option_initial.algorithm_phases[t.phase], + "default algorithm for phase already set") + assert (type(store_me) == "table", + "default algorithms must be loaded immediately") + InterfaceCore.option_initial.algorithm_phases[t.phase] = store_me + InterfaceCore.option_initial.algorithm_phases[t.phase .. " stack"] = { store_me } + else + InterfaceCore.option_initial.algorithm_phases[t.phase .. " stack"] = { + dummy = true -- Remove once Lua Link Bug is fixed + } + end + + return true +end + + + + +--- +-- This function is called by |declare| for ``collection kinds''. They +-- are detected by the presence of the field |layer| +-- in the table |t| passed to |declare|. See the class |Collection| +-- for details on what a collection and a collection kind is. +-- +-- The |key| field of the table |t| passed to this function is both +-- the name of the to-be-declared collection kind as well as the key +-- that is used on the display layer to indicate that a node or edge +-- belongs to a collection. +-- +-- \medskip +-- \noindent\textbf{The Display Layer.} +-- Let us first have a look at what happens on the display layer: +-- A key |t.key| is setup on the display layer that, when used inside +-- a graph drawing scope, starts a new collection of the specified +-- kind. ``Starts'' means that all nodes and edges mentioned in the +-- rest of the current option scope will belong to a new collection +-- of kind |t.key|. +-- % +--\begin{codeexample}[code only, tikz syntax=false] +--declare { key = "hyper", layer = 1 } +--\end{codeexample} +-- % +-- you can say on the \tikzname\ layer +-- % +--\begin{codeexample}[code only] +-- \graph { +-- a, b, c, d; +-- { [hyper] a, b, c } +-- { [hyper] b, c, d } +-- }; +--\end{codeexample} +-- +-- In this case, the nodes |a|, |b|, |c| will belong to a collection of +-- kind |hyper|. The nodes |b|, |c|, and |d| will (also) belong to +-- another collection of the same kind |hyper|. You can nest +-- collections; in this case, nodes will belong to several +-- collections. +-- +-- The effect of declaring a collection kind on the algorithm layer +-- it, first of all, that |scope.collections| will have a field named +-- by the collection kind. This field will store an array that +-- contains all collections that were declared as part of the +-- graph. For instance, |collections.hyper| will contain all +-- hyperedges, each of which is a table with the following fields: The +-- |vertices| and |edges| fields each contain arrays of all objects +-- being part of the collection. The |sub| field is an array of +-- ``subcollections'', that is, all collections that were started +-- inside another collection. (For the collection kinds |hyper| and +-- |same layer| this makes no sense, but subgraphs could, for instance, +-- be nested.) +-- +-- \medskip +-- \noindent\textbf{Rendering of Collections.} +-- For some kinds of collections, it makes sense to \emph{render} them, +-- but only after the graph drawing algorithm has run. For this +-- purpose, the binding layer will use a callback for each collection +-- kind and each collection, see the |Binding| class for details. +-- Suppose, for instance, you would +-- like hyperedges to be rendered. In this case, a graph drawing +-- algorithm should iterate over all collections of type |hyper| and +-- compute some hints on how to render the hyperedge and store this +-- information in the |generated_options| table of the hyperedge. Then, +-- the binding layer will ask the display layer to run some some code +-- that is able to read key--value pairs passed to +-- it (which are the key--value pairs of the |generated_options| table) +-- and use this information to nicely draw the hyperedge. +-- +-- The number |t.layer| determines in which order the different +-- collection kinds are rendered. +-- +-- The last parameter, the layer number, is used to specify the order +-- in which the different collection kinds are rendered. The higher the +-- number, the later the collection will be rendered. Thus, if there is +-- a collection kind with layer number 10 and another with layer number +-- 20, all collections of the first kind will be rendered first, +-- followed by all collections of the second kind. +-- +-- Collections whose layer kinds are non-negative get rendered +-- \emph{after} the nodes and edges have already been rendered. In +-- contrast, collections with a negative layer number get shown +-- ``below'' the nodes and edges. +-- +-- (You cannot call this function directly, it is included for +-- documentation purposes only.) +-- +-- @param t The table originally passed to |declare|. + +local function declare_collection_kind (t) + assert (type(t.layer) == "number", "layer must be a number") + + local layer = t.layer + local kind = t.key + local kinds = InterfaceCore.collection_kinds + local new_entry = { kind = kind, layer = layer } + + -- Insert into table part: + kinds[kind] = new_entry + + -- Insert into array part: + local found + for i=1,#kinds do + if kinds[i].layer > layer or (kinds[i].layer == layer and kinds[i].kind > kind) then + table.insert(kinds, i, new_entry) + return + end + end + + kinds[#kinds+1] = new_entry + + -- Bind + InterfaceCore.binding:declareCallback(t) + + return true +end + + + +-- Build in handlers: + +declare_handlers = { + { test = function (t) return t.algorithm_written_in_c end, handler = InterfaceToC.declare_algorithm_written_in_c }, + { test = function (t) return t.algorithm end, handler = declare_algorithm }, + { test = function (t) return t.layer end, handler = declare_collection_kind }, + { test = function (t) return true end, handler = declare_parameter } +} + + + + + + + + +--- +-- Finds a node by its name. This method should be used by algorithms +-- for which a node name is specified in some option and, thus, needs +-- to be converted to a vertex object during a run of the algorithm. +-- +-- @param name A node name +-- +-- @return The vertex of the given name in the syntactic digraph or +-- |nil|. + +function InterfaceToAlgorithms.findVertexByName(name) + return InterfaceCore.topScope().node_names[name] +end + + + + + +-- Helper function +local function add_to_collections(collection,where,what) + if collection then + LookupTable.addOne(collection[where],what) + add_to_collections(collection.parent,where,what) + end +end + +local unique_count = 1 + +--- +-- Generate a new vertex in the syntactic digraph. Calling this method +-- allows algorithms to create vertices that are not present in the +-- original input graph. Using the graph drawing coroutine, this +-- function will pass back control to the display layer in order to +-- render the vertex and, thereby, create precise size information +-- about it. +-- +-- Note that creating new vertices in the syntactic digraph while the +-- algorithm is already running is a bit at odds with the notion of +-- treating graph drawing as a series of graph transformations: For +-- instance, when a new vertex is created, the graph will (at least +-- temporarily) no longer be connected; even though an algorithm may +-- have requested that it should only be fed connected +-- graphs. Likewise, more complicated requirements like insisting on +-- the graph being a tree also cannot be met. +-- +-- For these reasons, the following happens, when a new vertex is +-- created using the function: +-- % +-- \begin{enumerate} +-- \item The vertex is added to the syntactic digraph. +-- \item It is added to all layouts on the current layout stack. When +-- a graph drawing algorithm is run, it is not necessarily run on the +-- original syntactic digraph. Rather, a sequence / stack of nested +-- layouts may currently +-- be processed and the vertex is added to all of them. +-- \item The vertex is added to both the |digraph| and the |ugraph| of +-- the current algorithm. +-- \end{enumerate} +-- +-- @param algorithm An algorithm for whose syntactic digraph the node +-- should be added +-- @param init A table of initial values for the node that is passed +-- to |Binding:createVertex|, see that function for details. +-- +-- @return The newly created node +-- +function InterfaceToAlgorithms.createVertex(algorithm, init) + + -- Setup + local scope = InterfaceCore.topScope() + local binding = InterfaceCore.binding + + -- Setup node + if not init.name then + init.name = "internal@gd@node@" .. unique_count + unique_count = unique_count + 1 + end + + -- Does vertex already exist? + assert (not scope.node_names[name], "node already created") + + if not init.shape or init.shape == "none" then + init.shape = "rectangle" + end + + -- Call binding + binding:createVertex(init) + + local v = assert(scope.node_names[init.name], "internal node creation failed") + + -- Add vertex to the algorithm's digraph and ugraph + algorithm.syntactic_component:add {v} + algorithm.digraph:add {v} + algorithm.ugraph:add {v} + + -- Compute bounding boxes: + LayoutPipeline.prepareBoundingBoxes(algorithm.rotation_info, algorithm.adjusted_bb, algorithm.digraph, {v}) + + -- Add the node to the layout stack: + add_to_collections(algorithm.layout, "vertices", v) + + algorithm.layout_graph:add { v } + + return v +end + + + +--- +-- Generate a new edge in the syntactic digraph. This method is quite +-- similar to |createVertex| and has the same effects with respect to +-- the edge: The edge is added to the syntactic digraph and also to +-- all layouts on the layout stack. Furthermore, appropriate edges are +-- added to the |digraph| and the |ugraph| of the algorithm currently +-- running. +-- +-- @param algorithm An algorithm for whose syntactic digraph the node should be added +-- @param tail A syntactic tail vertex +-- @param head A syntactic head vertex +-- @param init A table of initial values for the edge. +-- +-- The following fields are useful for |init|: +-- % +-- \begin{itemize} +-- \item |init.direction| If present, a direction for the edge. Defaults to "--". +-- \item |init.options| If present, some options for the edge. +-- \item |init.generated_options| A table that is passed back to the +-- display layer as a list of key-value pairs in the syntax of +-- |declare_parameter|. +-- \end{itemize} + +function InterfaceToAlgorithms.createEdge(algorithm, tail, head, init) + + init = init or {} + + -- Setup + local scope = InterfaceCore.topScope() + local binding = InterfaceCore.binding + local syntactic_digraph = algorithm.layout_graph + local syntactic_component = algorithm.syntactic_component + + assert (syntactic_digraph:contains(tail) and + syntactic_digraph:contains(head), + "attempting to create edge between nodes that are not in the syntactic digraph") + + local arc = syntactic_digraph:connect(tail, head) + + local edge = Edge.new { + head = head, + tail = tail, + direction = init.direction or "--", + options = init.options or algorithm.layout.options, + path = init.path, + generated_options = init.generated_options + } + + -- Add to arc + arc.syntactic_edges[#arc.syntactic_edges+1] = edge + + local s_arc = syntactic_component:connect(tail, head) + s_arc.syntactic_edges = arc.syntactic_edges + + -- Create Event + local e = InterfaceToDisplay.createEvent ("edge", { arc, #arc.syntactic_edges }) + edge.event = e + + -- Make part of collections + for _,c in ipairs(edge.options.collections) do + LookupTable.addOne(c.edges, edge) + end + + -- Call binding + binding.storage[edge] = {} + binding:everyEdgeCreation(edge) + + -- Add edge to digraph and ugraph + local direction = edge.direction + if direction == "->" then + algorithm.digraph:connect(tail, head) + elseif direction == "<-" then + algorithm.digraph:connect(head, tail) + elseif direction == "--" or direction == "<->" then + algorithm.digraph:connect(tail, head) + algorithm.digraph:connect(head, tail) + end + algorithm.ugraph:connect(tail, head) + algorithm.ugraph:connect(head, tail) + + -- Add edge to layouts + add_to_collections(algorithm.layout, "edges", edge) + +end + + + + + +-- Done + +return InterfaceToAlgorithms diff --git a/graphics/pgf/base/tex/generic/graphdrawing/lua/pgf/gd/interface/InterfaceToC.lua b/graphics/pgf/base/tex/generic/graphdrawing/lua/pgf/gd/interface/InterfaceToC.lua new file mode 100644 index 0000000000..282727b66a --- /dev/null +++ b/graphics/pgf/base/tex/generic/graphdrawing/lua/pgf/gd/interface/InterfaceToC.lua @@ -0,0 +1,78 @@ +-- Copyright 2012 by Till Tantau +-- +-- This file may be distributed an/or modified +-- +-- 1. under the LaTeX Project Public License and/or +-- 2. under the GNU Public License +-- +-- See the file doc/generic/pgf/licenses/LICENSE for more information + +-- @release $Header$ + + +--- +-- This table contains functions that are used (on the Lua side) to +-- prepare a graph for use in C and, vice versa, to translate back the +-- results of C to Lua. + +local InterfaceToC = {} + +-- Imports + +local lib = require "pgf.gd.lib" + + +--- +-- This function is called by |declare| for ``algorithm +-- keys'' where the algorithm is not written in Lua, but rather in the +-- programming language C. You do not call this function yourself; +-- |InterfaceFromC.h| will do it for you. Nevertheless, if you +-- provide a table to |declare| with the field +-- |algorithm_written_in_c| set, the following happens: The table's +-- |algorithm| field is set to an algorithm class object whose |run| +-- method calls the function passed via the +-- |algorithm_written_in_c| field. It will be called with the +-- following parameters (in that order): +-- % +-- \begin{enumerate} +-- \item The to-be-laid out digraph. This will not be the whole layout +-- graph (syntactic digraph) if preprocessing like decomposition into +-- connected components is used. +-- \item An array of the digraph's vertices, but with the table part +-- hashing vertex objects to their indices in the array part. +-- \item An array of the syntactic edges of the digraph. Like the +-- array, the table part will hash back the indices of the edge objects. +-- \item The algorithm object. +-- \end{enumerate} +-- +-- @param t The table originally passed to |declare|. + +function InterfaceToC.declare_algorithm_written_in_c (t) + t.algorithm = { + run = function (self) + local back_table = lib.icopy(self.ugraph.vertices) + for i,v in ipairs(self.ugraph.vertices) do + back_table[v] = i + end + local edges = {} + for _,a in ipairs(self.ugraph.arcs) do + local b = self.layout_graph:arc(a.tail,a.head) + if b then + lib.icopy(b.syntactic_edges, edges) + end + end + for i=1,#edges do + edges[edges[i]] = i + end + collectgarbage("stop") -- Remove once Lua Link Bug is fixed + t.algorithm_written_in_c (self.digraph, back_table, edges, self) + collectgarbage("restart") -- Remove once Lua Link Bug is fixed + end + } +end + + + +-- Done + +return InterfaceToC diff --git a/graphics/pgf/base/tex/generic/graphdrawing/lua/pgf/gd/interface/InterfaceToDisplay.lua b/graphics/pgf/base/tex/generic/graphdrawing/lua/pgf/gd/interface/InterfaceToDisplay.lua new file mode 100644 index 0000000000..23018efcaa --- /dev/null +++ b/graphics/pgf/base/tex/generic/graphdrawing/lua/pgf/gd/interface/InterfaceToDisplay.lua @@ -0,0 +1,1002 @@ +-- Copyright 2012 by Till Tantau +-- +-- This file may be distributed an/or modified +-- +-- 1. under the LaTeX Project Public License and/or +-- 2. under the GNU Public License +-- +-- See the file doc/generic/pgf/licenses/LICENSE for more information + +-- @release $Header$ + + + +--- +-- This class provides the interface between a display +-- layer (like \tikzname\ or a graph editor) and graph drawing +-- system. Another class, |InterfaceToAlgorithms|, binds the algorithm +-- layer (which are written in Lua) to the graph drawing system. +-- +-- The functions declared here are independent of the actual display +-- layer. Rather, the differences between the layers are encapsulated +-- by subclasses of the |Binding| class, see that class for +-- details. Thus, when a new display layer is written, the present +-- class is \emph{used}, but not \emph{modified}. Instead, only a new +-- binding is created and all display layer specific interaction is +-- put there. +-- +-- The job of this class is to provide convenient methods that can be +-- called by the display layer. For instance, it provides methods for +-- starting a graph drawing scope, managing the stack of such scope, +-- adding a node to a graph and so on. + +local InterfaceToDisplay = {} + +-- Namespace +require("pgf.gd.interface").InterfaceToDisplay = InterfaceToDisplay + + +-- Imports +local InterfaceCore = require "pgf.gd.interface.InterfaceCore" +local Scope = require "pgf.gd.interface.Scope" + +local Binding = require "pgf.gd.bindings.Binding" + +local Sublayouts = require "pgf.gd.control.Sublayouts" +local LayoutPipeline = require "pgf.gd.control.LayoutPipeline" + +local Digraph = require "pgf.gd.model.Digraph" +local Vertex = require "pgf.gd.model.Vertex" +local Edge = require "pgf.gd.model.Edge" +local Collection = require "pgf.gd.model.Collection" + +local Storage = require "pgf.gd.lib.Storage" +local LookupTable = require "pgf.gd.lib.LookupTable" +local Event = require "pgf.gd.lib.Event" + +local lib = require "pgf.gd.lib" + + +-- Forward declarations +local get_current_options_table +local render_collections +local push_on_option_stack +local vertex_created + +-- Local objects + +local phase_unique = {} -- a unique handle +local collections_unique = {} -- a unique handle +local option_cache = nil -- The option cache + + + + +--- +-- Initialize the binding. This function is called once by the display +-- layer at the very beginning. For instance, \tikzname\ does the +-- following call: +-- % +--\begin{codeexample}[code only, tikz syntax=false] +--InterfaceToDisplay.bind(require "pgf.gd.bindings.BindingToPGF") +--\end{codeexample} +-- +-- Inside this call, many standard declarations will be executed, that +-- is, the declared binding will be used immediately. +-- +-- Subsequently, the |binding| field of the |InterfaceCore| can be used. +-- +-- @param class A subclass of |Binding|. + +function InterfaceToDisplay.bind(class) + assert (not InterfaceCore.binding, "binding already initialized") + + -- Create a new object + InterfaceCore.binding = setmetatable({}, class) + + -- Load these libraries, which contain many standard declarations: + require "pgf.gd.model.library" + require "pgf.gd.control.library" +end + + + + +--- +-- Start a graph drawing scope. Note that this is not the same as +-- starting a subgraph / sublayout, which are local to a graph drawing +-- scope: When a new graph drawing scope is started, it is pushed on +-- top of a stack of graph drawing scopes and all other ``open'' +-- scopes are no longer directly accessible. All method calls to an +-- |Interface...| object will refer to this newly created scope until +-- either a new scope is opened or until the current scope is closed +-- once more. +-- +-- Each graph drawing scope comes with a syntactic digraph that is +-- build using methods like |addVertex| or |addEdge|. +-- +-- @param height The to-be-used height of the options stack. All +-- options above this height will be popped prior to attacking the +-- options to the syntactic digraph. + +function InterfaceToDisplay.beginGraphDrawingScope(height) + + -- Create a new scope table + local scope = Scope.new {} + + -- Setup syntactic digraph: + local g = scope.syntactic_digraph + + g.options = get_current_options_table(height) + g.syntactic_digraph = g + g.scope = scope + + -- Push scope: + InterfaceCore.scopes[#InterfaceCore.scopes + 1] = scope +end + + + +--- +-- Arranges the current graph using the specified algorithm and options. +-- +-- This function should be called after the graph drawing scope has +-- been opened and the syntactic digraph has been completely +-- specified. It will now start running the algorithm specified +-- through the |algorithm_phase| options. +-- +-- Internally, this function creates a coroutine that will run the current graph +-- drawing algorithm. Coroutines are needed since a graph drawing +-- algorithm may choose to create a new node. In this case, the +-- algorithm needs to be suspended and control must be returned back +-- to the display layer, so that the node can be typeset in order to +-- determine the precise size information. Once this is done, control +-- must be passed back to the exact point inside the algorithm where +-- the node was created. Clearly, all of these actions are exactly +-- what coroutines are for. +-- +-- @return Time it took to run the algorithm + +function InterfaceToDisplay.runGraphDrawingAlgorithm() + + -- Time things + local start = os.clock() + + -- Setup + local scope = InterfaceCore.topScope() + assert(not scope.coroutine, "coroutine already created for current gd scope") + + -- The actual drawing function + local function run () + if #scope.syntactic_digraph.vertices == 0 then + -- Nothing needs to be done + return + end + + LayoutPipeline.run(scope) + end + + scope.coroutine = coroutine.create(run) + + -- Run it: + InterfaceToDisplay.resumeGraphDrawingCoroutine() + + -- End timing: + local stop = os.clock() + + return stop - start +end + + +--- +-- Resume the graph drawing coroutine. +-- +-- This function is the work horse of the coroutine management. It +-- gets called whenever control passes back from the display layer to +-- the algorithm level. We resume the graph drawing coroutine so that the +-- algorithm can start/proceed. The tricky part is when the algorithm +-- yields, but is not done. In this case, the code needed for creating +-- a new node is passed back to the display layer through the binding, +-- which must then execute the code and then resuming the coroutine. +-- +function InterfaceToDisplay.resumeGraphDrawingCoroutine() + + -- Setup + local scope = InterfaceCore.topScope() + local binding = InterfaceCore.binding + + -- Asserts + assert(scope.coroutine, "coroutine not created for current gd scope") + + -- Run + local ok, text = coroutine.resume(scope.coroutine) + assert(ok, text) + if coroutine.status(scope.coroutine) ~= "dead" then + -- Ok, ask binding to continue + binding:resumeGraphDrawingCoroutine(text) + end +end + + + +--- Ends the current graph drawing scope. +-- +function InterfaceToDisplay.endGraphDrawingScope() + assert(#InterfaceCore.scopes > 0, "no gd scope open") + InterfaceCore.scopes[#InterfaceCore.scopes] = nil -- pop +end + + + + +--- +-- Creates a new vertex in the syntactic graph of the current graph +-- drawing scope. The display layer should call this function for each +-- node of the graph. The |name| must be a unique string identifying +-- the node. The newly created vertex will be added to the syntactic +-- digraph. The binding function |everyVertexCreation| will then be +-- called, allowing the binding to store information regarding the newly +-- created vertex. +-- +-- For each vertex an event will be created in the event +-- sequence. This event will have the kind |"node"| and its +-- |parameter| will be the vertex. +-- +-- @param name Name of the vertex. +-- +-- @param shape The shape of the vertex such as |"circle"| or +-- |"rectangle"|. This shape may help a graph drawing algorithm +-- figuring out how the node should be placed. +-- +-- @param path A |Path| object representing the vertex's path. +-- +-- @param height The to-be-used height of the options stack. All +-- options above this height will be popped prior to attacking the +-- options to the syntactic digraph. +-- +-- @param binding_infos These options are passed to and are specific +-- to the current |Binding|. +-- +-- @param anchors A table of anchors (mapping anchor positions to +-- |Coordinates|). + + +function InterfaceToDisplay.createVertex(name, shape, path, height, binding_infos, anchors) + + -- Setup + local scope = InterfaceCore.topScope() + local binding = InterfaceCore.binding + + -- Does vertex already exist? + local v = scope.node_names[name] + assert (not v or not v.created_on_display_layer, "node already created") + + -- Create vertex + if not v then + v = Vertex.new { + name = name, + shape = shape, + kind = "node", + path = path, + options = get_current_options_table(height), + anchors = anchors, + } + + vertex_created(v,scope) + else + assert(v.kind == "subgraph node", "subgraph node expected") + v.shape = shape + v.path = path + v.anchors = anchors + end + + v.created_on_display_layer = true + + -- Call binding + binding.storage[v] = binding_infos + binding:everyVertexCreation(v) +end + + +-- This is a helper function +function vertex_created(v,scope) + + -- Create Event + local e = InterfaceToDisplay.createEvent ("node", v) + v.event = e + + -- Create name lookup + scope.node_names[v.name] = v + + -- Add vertex to graph + scope.syntactic_digraph:add {v} + + -- Add to collections + for _,c in ipairs(v.options.collections) do + LookupTable.addOne(c.vertices, v) + end + +end + + + +--- +-- Creates a new vertex in the syntactic graph of the current graph +-- drawing scope that is a subgraph vertex. Such a vertex +-- ``surrounds'' the vertices of a subgraph. The special property of a +-- subgraph node opposed to a normal node is that it is created only +-- after the subgraph has been laid out. However, the difference to a +-- collection like |hyper| is that the node is available immediately as +-- a normal node in the sense that you can connect edges to it. +-- +-- What happens internally is that subgraph nodes get ``registered'' +-- immediately both on the display level and on the algorithm level, +-- but the actual node is only created inside the layout pipeline +-- using a callback of the binding. The present function is used to +-- perform this registering. The node creation happens when the +-- innermost layout in which the subgraph node is declared has +-- finished. For each subgraph node, a collection is created that +-- contains all vertices (and edges) being part of the subgraph. For +-- this reason, this method is a |push...| method, since it pushes +-- something on the options stack. +-- +-- The |init| parameter will be used during the creation of the node, +-- see |Binding:createVertex| for details on the fields. Note that +-- |init.text| is often not displayed for such ``vast'' nodes as those +-- created for whole subgraphs, but a shape may use it nevertheless +-- (for instance, one might display this text at the top of the node +-- or, in case of a \textsc{uml} package, in a special box above the +-- actual node). +-- +-- The |init.generated_options| will be augmented by additional +-- key--value pairs when the vertex is created: +-- % +-- \begin{itemize} +-- \item The key |subgraph point cloud| will have as its value a +-- string that is be a list of points (without separating commas) +-- like |"(10pt,20pt)(0pt,0pt)(30pt,40pt)"|, always in +-- this syntax. The list will contain all points inside the +-- subgraph. In particular, a bounding box around these points will +-- encompass all nodes and bend points of the subgraph. +-- The bounding box of this point cloud is guaranteed to be centered on +-- the origin. +-- \item The key |subgraph bounding box width| will have as its value +-- the width of a bounding box (in \TeX\ points, as a string with the +-- suffix |"pt"|). +-- \item The key |subgraph bounding box height| stores the height of a +-- bounding box. +-- \end{itemize} +-- +-- @param name The name of the node. +-- @param height Height of the options stack. Note that this method +-- pushes something (namely a collection) on the options stack. +-- @param info A table passed to |Binding:createVertex|, see that function. +-- +function InterfaceToDisplay.pushSubgraphVertex(name, height, info) + + -- Setup + local scope = InterfaceCore.topScope() + local binding = InterfaceCore.binding + + -- Does vertex already exist? + assert (not scope.node_names[name], "node already created") + + -- Create vertex + local v = Vertex.new { + name = name, + kind = "subgraph node", + options = get_current_options_table(height-1) + } + + vertex_created(v,scope) + + -- Store info + info.generated_options = info.generated_options or {} + info.name = name + v.subgraph_info = info + + -- Create collection and link it to v + local _, _, entry = InterfaceToDisplay.pushOption(InterfaceCore.subgraph_node_kind, nil, height) + v.subgraph_collection = entry.value + v.subgraph_collection.subgraph_node = v + + -- Find parent collection in options stack: + local collections = v.options.collections + for i=#collections,1,-1 do + if collections[i].kind == InterfaceCore.sublayout_kind then + v.subgraph_collection.parent_layout = collections[i] + break + end + end +end + + + +--- +-- Add options for an already existing vertex. +-- +-- This function allows you to add options to an already existing +-- vertex. The options that will be added are all options on the +-- current options stack; they will overwrite existing options of the +-- same name. For collections, the vertex stays in all collections it +-- used to, it is only added to all collections that are currently on +-- the options stack. +-- +-- @param name Name of the vertex. +-- @param height The option stack height. + +function InterfaceToDisplay.addToVertexOptions(name, height) + + -- Setup + local scope = InterfaceCore.topScope() + + -- Does vertex already exist? + local v = assert (scope.node_names[name], "node is missing, cannot add options") + + v.options = get_current_options_table(height, v.options) + + -- Add to collections + for _,c in ipairs(v.options.collections) do + LookupTable.addOne(c.vertices, v) + end + +end + + + + + +--- +-- Creates a new edge in the syntactic graph of the current graph +-- drawing scope. The display layer should call this function for each +-- edge that is created. Both the |from| vertex and the |to| vertex +-- must exist (have been created through |createVertex|) prior to your +-- being able to call this function. +-- +-- After the edge has been created, the binding layer's function +-- |everyEdgeCreation| will be called, allowing the binding layer to +-- store information about the edge. +-- +-- For each edge an event is created, whose kind is |"edge"| and whose +-- |parameter| is a two-element array whose first entry is the edge's +-- arc in the syntactic digraph and whose second entry is the position +-- of the edge in the arc's array of syntactic edges. +-- +-- @param tail Name of the node the edge begins at. +-- @param head Name of the node the edge ends at. +-- @param direction Direction of the edge (e.g. |--| for an undirected edge +-- or |->| for a directed edge from the first to the second +-- node). +-- @param height The option stack height, see for instance |createVertex|. +-- +-- @param binding_infos These options will be stored in the |storage| +-- of the vertex at the field index by the binding. + +function InterfaceToDisplay.createEdge(tail, head, direction, height, binding_infos) + + -- Setup + local scope = InterfaceCore.topScope() + local binding = InterfaceCore.binding + + -- Does vertex already exist? + local h = scope.node_names[head] + local t = scope.node_names[tail] + assert (h and t, "attempting to create edge between nodes that are not in the graph") + + -- Create Arc object + local arc = scope.syntactic_digraph:connect(t, h) + + -- Create Edge object + local edge = Edge.new { + head = h, + tail = t, + direction = direction, + options = get_current_options_table(height) + } + + -- Add to arc + arc.syntactic_edges[#arc.syntactic_edges+1] = edge + + -- Create Event + local e = InterfaceToDisplay.createEvent ("edge", { arc, #arc.syntactic_edges }) + edge.event = e + + -- Make part of collections + for _,c in ipairs(edge.options.collections) do + LookupTable.addOne(c.edges, edge) + end + + -- Call binding + binding.storage[edge] = binding_infos + binding:everyEdgeCreation(edge) + +end + + + + + +--- +-- Push an option to the stack of options. +-- +-- As a graph is parsed, a stack of ``current options'' +-- is created. To add something to this table, the display layers may +-- call the method |pushOption|. To pop something from this stack, +-- just set the |height| value during the next push to the position to +-- which you actually wish to push something; everything above and +-- including this position will be popped from the stack. +-- +-- When an option is pushed, several additional options may also be +-- pushed, namely whenever the option has a |use| field set. These +-- additional options may, in turn, also push new options. Because of +-- this, this function returns a new stack height, representing the +-- resulting stack height. +-- +-- In addition to this stack height, this function returns a Boolean +-- value indicating whether a ``main algorithm phase was set''. This +-- happens whenever a key is executed (directly or indirectly through +-- the |use| field) that selects an algorithm for the ``main'' +-- algorithm phase. This information may help the caller to setup the +-- graph drawing scopes correctly. +-- +-- @param key A parameter (must be a string). +-- @param value A value (can be anything). If it is a string, it will +-- be converted to whatever the key expects. +-- @param height A stack height at which to insert the key. Everything +-- above this height will be removed. +-- +-- @return A new stack height +-- @return A Boolean that is |true| if the main algorithm phase was +-- set by the option or one option |use|d by it. +-- @return The newly created entry on the stack. If more entries are +-- created through the use of the |use| field, the original entry is +-- returned nevertheless. + + +function InterfaceToDisplay.pushOption(key, value, height) + assert(type(key) == "string", "illegal key") + + local key_record = assert(InterfaceCore.keys[key], "unknown key") + local main_phase_set = false + + if value == nil and key_record.default then + value = key_record.default + end + + -- Find out what kind of key we are pushing: + + if key_record.algorithm then + -- Push a phase + if type(InterfaceCore.algorithm_classes[key]) == "function" then + -- Call the constructor function + InterfaceCore.algorithm_classes[key] = InterfaceCore.algorithm_classes[key]() + end + + local algorithm = InterfaceCore.algorithm_classes[key] + + assert (algorithm, "algorithm class not found") + + push_on_option_stack(phase_unique, + { phase = value or key_record.phase, algorithm = algorithm }, + height) + + if key_record.phase == "main" then + main_phase_set = true + end + + elseif key_record.layer then + -- Push a collection + local stack = InterfaceCore.option_stack + local scope = InterfaceCore.topScope() + + -- Get the stack above "height": + local options = get_current_options_table(height-1) + + -- Create the collection event + local event = InterfaceToDisplay.createEvent ("collection", key) + + -- Create collection object: + local collection = Collection.new { kind = key, options = options, event = event } + + -- Store in collections table of current scope: + local collections = scope.collections[key] or {} + collections[#collections + 1] = collection + scope.collections[key] = collections + + -- Build collection tree + collection:registerAsChildOf(options.collections[#options.collections]) + + -- Push on stack + push_on_option_stack(collections_unique, collection, height) + + else + + -- A normal key + push_on_option_stack(key, InterfaceCore.convert(value, InterfaceCore.keys[key].type), height) + + end + + local newly_created = InterfaceCore.option_stack[#InterfaceCore.option_stack] + + -- Now, push use keys: + local use = key_record.use + if key_record.use then + local flag + for _,u in ipairs(InterfaceCore.keys[key].use) do + local use_k = u.key + local use_v = u.value + if type(use_k) == "function" then + use_k = use_k(value) + end + if type(use_v) == "function" then + use_v = use_v(value) + end + height, flag = InterfaceToDisplay.pushOption(use_k, use_v, height+1) + main_phase_set = main_phase_set or flag + end + end + + return height, main_phase_set, newly_created +end + + +--- +-- Push a layout on the stack of options. As long as this layout is on +-- the stack, all vertices and edges will be part of this layout. For +-- details on layouts, please see |Sublayouts|. +-- +-- @param height A stack height at which to insert the key. Everything +-- above this height will be removed. + +function InterfaceToDisplay.pushLayout(height) + InterfaceToDisplay.pushOption(InterfaceCore.sublayout_kind, nil, height) +end + + + +--- +-- Creates an event and adds it to the event string of the current scope. +-- +-- @param kind Name/kind of the event. +-- @param parameters Parameters of the event. +-- +-- @return The newly pushed event +-- +function InterfaceToDisplay.createEvent(kind, param) + local scope = InterfaceCore.topScope() + local n = #scope.events + 1 + local e = Event.new { kind = kind, parameters = param, index = n } + scope.events[n] = e + + return e +end + + + +--- +-- This method allows you to query the table of all declared keys. It +-- contains them both as an array and also as a table index by the +-- keys's names. In particular, you can then iterate over it using +-- |ipairs| and you can check whether a key is defined by accessing +-- the table at the key's name. Each entry of the table is the +-- original table passed to |InterfaceToAlgorithms.declare|. +-- +-- @return A lookup table of all declared keys. + +function InterfaceToDisplay.getDeclaredKeys() + return InterfaceCore.keys +end + + + + +--- +-- Renders the graph. +-- +-- This function is called after the graph has been laid out by the +-- graph drawing algorithms. It will trigger a sequence of calls to +-- the binding layer that will, via callbacks, start rendering the +-- whole graph. +-- +-- In detail, this function calls: +-- % +--\begin{codeexample}[code only, tikz syntax=false] +--local binding = InterfaceCore.binding +-- +--binding:renderStart() +--render_vertices() +--render_edges() +--render_collections() +--binding:renderStop() +--\end{codeexample} +-- +-- Here, the |render_...| functions are local, internal functions that are, +-- nevertheless, documented here. +-- +-- @param name Returns the algorithm class that has been declared using +-- |declare| under the given name. + +function InterfaceToDisplay.renderGraph() + local scope = InterfaceCore.topScope() + local syntactic_digraph = scope.syntactic_digraph + + local binding = InterfaceCore.binding + + binding:renderStart() + render_vertices(syntactic_digraph.vertices) + render_edges(syntactic_digraph.arcs) + render_collections(scope.collections) + binding:renderStop() +end + + + + + +--- +-- Render the vertices after the graph drawing algorithm has +-- finished. This function is local and internal and included only for +-- documenting the call graph. +-- +-- When the graph drawing algorithm is done, the interface will start +-- rendering the vertices by calling appropriate callbacks of the +-- binding layer. +-- +-- Consider the following code: +-- % +--\begin{codeexample}[code only] +--\graph [... layout] { +-- a -- b -- c -- d; +--}; +--\end{codeexample} +-- +-- In this case, after the graph drawing algorithm has run, the +-- present function will call: +-- % +--\begin{codeexample}[code only, tikz syntax=false] +--local binding = InterfaceCore.binding +-- +--binding:renderVerticesStart() +--binding:renderVertex(vertex_a) +--binding:renderVertex(vertex_b) +--binding:renderVertex(vertex_c) +--binding:renderVertex(vertex_d) +--binding:renderVerticesStop() +--\end{codeexample} +-- +-- @param vertices An array of all vertices in the syntactic digraph. + +function render_vertices(vertices) + InterfaceCore.binding:renderVerticesStart() + for _,vertex in ipairs(vertices) do + InterfaceCore.binding:renderVertex(vertex) + end + InterfaceCore.binding:renderVerticesStop() +end + + +--- +-- Render the collections whose layer is not |0|. This local, internal +-- function is called to render the different collection kinds. +-- +-- Collection kinds rendered in the order provided by the |layer| +-- field passed to |declare| during the declaration of the collection +-- kind, see also |declare_collection|. If several collection kinds +-- have the same layer, they are rendered in lexicographical ordering +-- (to ensure that they are always rendered in the same order). +-- +-- Consider the following code: +-- % +--\begin{codeexample}[code only, tikz syntax=false] +--declare { key = "hyper", layer = 1 } +--\end{codeexample} +-- you can say on the \tikzname\ layer +--\begin{codeexample}[code only] +--\graph { +-- a, b, c, d; +-- { [hyper] a, b, c } +-- { [hyper] b, c, d } +--}; +--\end{codeexample} +-- +-- In this case, after the graph drawing algorithm has run, the +-- present function will call: +-- +--\begin{codeexample}[code only, tikz syntax=false] +--local binding = InterfaceCore.binding +-- +--binding:renderCollectionStartKind("hyper", 1) +--binding:renderCollection(collection_containing_abc) +--binding:renderCollection(collection_containing_bcd) +--binding:renderCollectionStopKind("hyper", 1) +--\end{codeexample} +-- +-- @param collections The |collections| table of the current scope. + +function render_collections(collections) + local kinds = InterfaceCore.collection_kinds + local binding = InterfaceCore.binding + + for i=1,#kinds do + local kind = kinds[i].kind + local layer = kinds[i].layer + + if layer ~= 0 then + binding:renderCollectionStartKind(kind, layer) + for _,c in ipairs(collections[kind] or {}) do + binding:renderCollection(c) + end + binding:renderCollectionStopKind(kind, layer) + end + end +end + + +--- +-- Render the syntactic edges of a graph after the graph drawing +-- algorithm has finished. This function is local and internal and included only +-- for documenting the call graph. +-- +-- When the graph drawing algorithm is done, the interface will first +-- rendering the vertices using |render_vertices|, followed by calling +-- this function, which in turn calls appropriate callbacks to the +-- binding layer. +-- +-- Consider the following code: +-- % +--\begin{codeexample}[code only] +-- \graph [... layout] { +-- a -- b -- c -- d; +-- }; +--\end{codeexample} +-- +-- In this case, after the graph drawing algorithm has run, the +-- present function will call: +-- % +--\begin{codeexample}[code only, tikz syntax=false] +-- local binding = InterfaceCore.binding +-- +-- binding:renderEdgesStart() +-- binding:renderEdge(edge_from_a_to_b) +-- binding:renderEdge(edge_from_b_to_c) +-- binding:renderEdge(edge_from_c_to_d) +-- binding:renderEdgesStop() +--\end{codeexample} +-- +-- @param arcs The array of arcs of the syntactic digraph. + +function render_edges(arcs) + InterfaceCore.binding:renderEdgesStart() + for _,a in ipairs(arcs) do + for _,e in ipairs (a.syntactic_edges) do + InterfaceCore.binding:renderEdge(e) + end + end + InterfaceCore.binding:renderEdgesStop() +end + + +local aliases = InterfaceCore.option_aliases +local option_initial = InterfaceCore.option_initial + +local option_metatable = { + __index = + function (t, key) + local k = aliases[key] + if k then + local v = (type(k) == "string" and t[k]) or (type(k) == "function" and k(t)) or nil + if v ~= nil then + return v + end + end + return option_initial[key] + end +} + + +--- +-- Get the current options table. +-- +-- An option table can be accessed like a normal table; however, there +-- is a global fallback for this table. If an index is not defined, +-- the value of this index in the global fallback table is used. (This +-- reduces the overall amount of option keys that need to be stored +-- with object.) +-- +-- (This function is local and internal and included only for documentation +-- purposes.) +-- +-- @param height The stack height for which the option table is +-- required. +-- @param table If non |nil|, the options will be added to this +-- table. +-- +-- @return The option table as described above. + +function get_current_options_table (height, table) + local stack = InterfaceCore.option_stack + assert (height >= 0 and height <= #stack, "height value out of bounds") + + if height == InterfaceCore.option_cache_height and not table then + return option_cache + else + -- Clear superfluous part of stack + for i=#stack,height+1,-1 do + stack[i] = nil + end + + -- Build options table + local cache + if not table then + cache = setmetatable( + { + algorithm_phases = setmetatable({}, InterfaceCore.option_initial.algorithm_phases), + collections = {} + }, option_metatable) + else + cache = lib.copy(table) + cache.algorithm_phases = lib.copy(cache.algorithm_phases) + cache.collections = lib.copy(cache.collections) + end + + local algorithm_phases = cache.algorithm_phases + local collections = cache.collections + local keys = InterfaceCore.keys + + local function handle (k, v) + if k == phase_unique then + algorithm_phases[v.phase] = v.algorithm + local phase_stack = v.phase .. " stack" + local t = rawget(algorithm_phases, phase_stack) + if not t then + t = algorithm_phases[phase_stack] + assert(type(t) == "table", "unknown phase") + t = lib.copy(t) + algorithm_phases[phase_stack] = t + end + t[#t + 1] = v.algorithm + elseif k == collections_unique then + LookupTable.addOne(collections, v) + else + cache[k] = v + end + end + + for _,s in ipairs(stack) do + handle (s.key, s.value) + end + + -- Cache it, if this was not added: + if not table then + InterfaceCore.option_cache_height = height + option_cache = cache + end + + return cache + end +end + + + +-- A helper function + +function push_on_option_stack(key, value, height) + local stack = InterfaceCore.option_stack + + assert (type(height) == "number" and height > 0 and height <= #stack + 1, + "height value out of bounds") + + -- Clear superfluous part of stack + for i=#stack,height+1,-1 do + stack[i] = nil + end + + stack[height] = { key = key, value = value } + InterfaceCore.option_cache_height = nil -- invalidate cache +end + + + +-- Done + +return InterfaceToDisplay diff --git a/graphics/pgf/base/tex/generic/graphdrawing/lua/pgf/gd/interface/Scope.lua b/graphics/pgf/base/tex/generic/graphdrawing/lua/pgf/gd/interface/Scope.lua new file mode 100644 index 0000000000..a750308e69 --- /dev/null +++ b/graphics/pgf/base/tex/generic/graphdrawing/lua/pgf/gd/interface/Scope.lua @@ -0,0 +1,92 @@ +-- Copyright 2012 by Till Tantau +-- +-- This file may be distributed an/or modified +-- +-- 1. under the LaTeX Project Public License and/or +-- 2. under the GNU Public License +-- +-- See the file doc/generic/pgf/licenses/LICENSE for more information + +-- @release $Header$ + + + +--- +-- In theory, graph drawing algorithms take graphs as input and +-- output graphs embedded into the plane as output. In practice, however, +-- the input to a graph drawing algorithm is not ``just'' the +-- graph. Rather, additional information about the graph, in particular +-- about the way the user specified the graph, is also important to many +-- graph drawing algorithms. +-- +-- The graph drawing system gathers both the original input graph as well +-- as all additional information that is provided in the graph drawing +-- scope inside a scope table. The object has a number of fields that +-- inform an algorithm about the input. +-- +-- For each graph drawing scope, a new |Scope| object is +-- created. Graph drawing scopes are kept track of using a stack, but +-- only the top of this stack is available to the interface classes. +-- +-- @field syntactic_digraph The syntactic digraph is a digraph that +-- faithfully encodes the way the input graph is represented +-- syntactically. However, this does not mean that the syntactic +-- digraph contains the actual textual representation of the input +-- graph. Rather, when an edge is specified as, say, |a <- b|, the +-- syntactic digraph will contains an arc from |a| to |b| with an edge +-- object attached to it that is labeled as a ``backward'' +-- edge. Similarly, an edge |a -- b| is also stored as a directed arc +-- from |a| to |b| with the label |--| attached to it. Algorithms will +-- often be more interested graphs derived from the syntactic digraph +-- such as its underlying undirected graph. These derived graphs are +-- made accessible by the graph drawing engine during the preprocessing. +-- +-- @field events An array of |Event| objects. These objects, see the +-- |Event| class for details, are created during the parsing of the +-- input graph. +-- +-- @field node_names A table that maps the names of nodes to node +-- objects. Every node must have a unique name. +-- +-- @field coroutine A Lua coroutine that is used internally to allow +-- callbacks to the display layer to be issued deep down during a run +-- of an algorithm. +-- +-- @field collections The collections specified inside the scope, see +-- the |Collection| class. + +local Scope = {} +Scope.__index = Scope + +-- Namespace +require("pgf.gd.interface").Scope = Scope + +-- Imports +local lib = require "pgf.gd.lib" +local Storage = require "pgf.gd.lib.Storage" + +local Digraph = require "pgf.gd.model.Digraph" + +--- +-- Create a new |Scope| object. +-- +-- @param initial A table of initial values for the newly created +-- |Scope| object. +-- +-- @return The new scope object. + +function Scope.new(initial) + return setmetatable(lib.copy(initial, + { + syntactic_digraph = Digraph.new{}, + events = {}, + node_names = {}, + coroutine = nil, + collections = {}, + }), Scope) +end + + +-- Done + +return Scope diff --git a/graphics/pgf/base/tex/generic/graphdrawing/lua/pgf/gd/layered.lua b/graphics/pgf/base/tex/generic/graphdrawing/lua/pgf/gd/layered.lua new file mode 100644 index 0000000000..6988e7a3d9 --- /dev/null +++ b/graphics/pgf/base/tex/generic/graphdrawing/lua/pgf/gd/layered.lua @@ -0,0 +1,159 @@ +-- Copyright 2012 by Till Tantau +-- +-- This file may be distributed an/or modified +-- +-- 1. under the LaTeX Project Public License and/or +-- 2. under the GNU Public License +-- +-- See the file doc/generic/pgf/licenses/LICENSE for more information + +--- @release $Header$ + + +local layered = {} + +-- Namespace + +require("pgf.gd").layered = layered + + +local lib = require "pgf.gd.lib" +local Storage = require "pgf.gd.lib.Storage" + +-- +-- This file defines some basic functions to compute and/or set the +-- ideal distances between nodes of any kind of layered drawing of a +-- graph. + + +--- +-- Compute the ideal distance between two siblings +-- +-- @param paddings A |Storage| object in which the computed distances +-- (paddings) are stored. +-- @param graph The graph object +-- @param n1 The first node +-- @param n2 The second node + +function layered.ideal_sibling_distance (paddings, graph, n1, n2) + local ideal_distance + local sep + + local n1_is_node = n1.kind == "node" + local n2_is_node = n2.kind == "node" + + if not n1_is_node and not n2_is_node then + ideal_distance = graph.options['sibling distance'] + sep = graph.options['sibling post sep'] + + graph.options['sibling pre sep'] + else + if n1_is_node then + ideal_distance = lib.lookup_option('sibling distance', n1, graph) + else + ideal_distance = lib.lookup_option('sibling distance', n2, graph) + end + sep = (n1_is_node and lib.lookup_option('sibling post sep', n1, graph) or 0) + + (n2_is_node and lib.lookup_option('sibling pre sep', n2, graph) or 0) + end + + return math.max(ideal_distance, sep + + ((n1_is_node and paddings[n1].sibling_post) or 0) - + ((n2_is_node and paddings[n2].sibling_pre) or 0)) +end + + + +--- +-- Compute the baseline distance between two layers +-- +-- The "baseline" distance is the distance between two layers that +-- corresponds to the distance of the two layers if the nodes where +-- "words" on two adjacent lines. In this case, the distance is +-- normally the layer_distance, but will be increased such that if we +-- draw a horizontal line below the deepest character on the first +-- line and a horizontal line above the highest character on the +-- second line, the lines will have a minimum distance of layer sep. +-- +-- Since each node on the lines might have a different layer sep and +-- layer distance specified, the maximum over all the values is taken. +-- +-- @param paddings A |Storage| object in which the distances +-- (paddings) are stored. +-- @param graph The graph in which the nodes reside +-- @param l1 An array of the nodes of the first layer +-- @param l2 An array of the nodes of the second layer + +function layered.baseline_distance (paddings, graph, l1, l2) + + if #l1 == 0 or #l2 == 0 then + return 0 + end + + local layer_distance = -math.huge + local layer_pre_sep = -math.huge + local layer_post_sep = -math.huge + + local max_post = -math.huge + local min_pre = math.huge + + for _,n in ipairs(l1) do + layer_distance = math.max(layer_distance, lib.lookup_option('level distance', n, graph)) + layer_post_sep = math.max(layer_post_sep, lib.lookup_option('level post sep', n, graph)) + if n.kind == "node" then + max_post = math.max(max_post, paddings[n].layer_post) + end + end + + for _,n in ipairs(l2) do + layer_pre_sep = math.max(layer_pre_sep, lib.lookup_option('level pre sep', n, graph)) + if n.kind == "node" then + min_pre = math.min(min_pre, paddings[n].layer_pre) + end + end + + return math.max(layer_distance, layer_post_sep + layer_pre_sep + max_post - min_pre) +end + + + +--- +-- Position nodes in layers using baselines +-- +-- @param layers A |Storage| object assigning layers to vertices. +-- @param paddings A |Storage| object storing the computed distances +-- (paddings). +-- @param graph The graph in which the nodes reside + +function layered.arrange_layers_by_baselines (layers, paddings, graph) + + local layer_vertices = Storage.newTableStorage() + + -- Decompose into layers: + for _,v in ipairs(graph.vertices) do + table.insert(layer_vertices[layers[v]], v) + end + + if #layer_vertices > 0 then -- sanity check + -- Now compute ideal distances and store + local height = 0 + + for _,v in ipairs(layer_vertices[1]) do + v.pos.y = 0 + end + + for i=2,#layer_vertices do + height = height + layered.baseline_distance(paddings, graph, layer_vertices[i-1], layer_vertices[i]) + + for _,v in ipairs(layer_vertices[i]) do + v.pos.y = height + end + end + end +end + + + + +-- Done + +return layered diff --git a/graphics/pgf/base/tex/generic/graphdrawing/lua/pgf/gd/layered/CrossingMinimizationGansnerKNV1993.lua b/graphics/pgf/base/tex/generic/graphdrawing/lua/pgf/gd/layered/CrossingMinimizationGansnerKNV1993.lua new file mode 100644 index 0000000000..da9ff9698c --- /dev/null +++ b/graphics/pgf/base/tex/generic/graphdrawing/lua/pgf/gd/layered/CrossingMinimizationGansnerKNV1993.lua @@ -0,0 +1,342 @@ +-- Copyright 2011 by Jannis Pohlmann +-- +-- This file may be distributed and/or modified +-- +-- 1. under the LaTeX Project Public License and/or +-- 2. under the GNU Public License +-- +-- See the file doc/generic/pgf/licenses/LICENSE for more information + +-- @release $Header$ + + + +local CrossingMinimizationGansnerKNV1993 = {} + + +-- Imports + +local lib = require "pgf.gd.lib" +local DepthFirstSearch = require "pgf.gd.lib.DepthFirstSearch" + + + +function CrossingMinimizationGansnerKNV1993:run() + + self:computeInitialRankOrdering() + + local best_ranking = self.ranking:copy() + local best_crossings = self:countRankCrossings(best_ranking) + + for iteration=1,24 do + local direction = (iteration % 2 == 0) and 'down' or 'up' + + self:orderByWeightedMedian(direction) + self:transpose(direction) + + local current_crossings = self:countRankCrossings(self.ranking) + + if current_crossings < best_crossings then + best_ranking = self.ranking:copy() + best_crossings = current_crossings + end + end + + self.ranking = best_ranking:copy() + + return self.ranking +end + + + +function CrossingMinimizationGansnerKNV1993:computeInitialRankOrdering() + + local best_ranking = self.ranking:copy() + local best_crossings = self:countRankCrossings(best_ranking) + + for _,direction in ipairs({'down', 'up'}) do + + local function init(search) + for i=#self.graph.nodes,1,-1 do + local node = self.graph.nodes[i] + if direction == 'down' then + if node:getInDegree() == 0 then + search:push(node) + search:setDiscovered(node) + end + else + if node:getOutDegree() == 0 then + search:push(node) + search:setDiscovered(node) + end + end + end + end + + local function visit(search, node) + search:setVisited(node, true) + + local rank = self.ranking:getRank(node) + local pos = self.ranking:getRankSize(rank) + self.ranking:setRankPosition(node, pos) + + if direction == 'down' then + local out = node:getOutgoingEdges() + for i=#out,1,-1 do + local neighbour = out[i]:getNeighbour(node) + if not search:getDiscovered(neighbour) then + search:push(neighbour) + search:setDiscovered(neighbour) + end + end + else + local into = node:getIncomingEdges() + for i=#into,1,-1 do + local neighbour = into[i]:getNeighbour(node) + if not search:getDiscovered(neighbour) then + search:push(neighbour) + search:setDiscovered(neighbour) + end + end + end + end + + DepthFirstSearch.new(init, visit):run() + + local crossings = self:countRankCrossings(self.ranking) + + if crossings < best_crossings then + best_ranking = self.ranking:copy() + best_crossings = crossings + end + end + + self.ranking = best_ranking:copy() + +end + + + +function CrossingMinimizationGansnerKNV1993:countRankCrossings(ranking) + + local crossings = 0 + + local ranks = ranking:getRanks() + + for rank_index = 2, #ranks do + local nodes = ranking:getNodes(ranks[rank_index]) + for i = 1, #nodes-1 do + for j = i+1, #nodes do + local v = nodes[i] + local w = nodes[j] + + -- TODO Jannis: We are REQUIRED to only check edges that lead to nodes + -- on the next or previous rank, depending on the sweep direction!!!! + local cn_vw = self:countNodeCrossings(ranking, v, w, 'down') + + crossings = crossings + cn_vw + end + end + end + + return crossings +end + + + +function CrossingMinimizationGansnerKNV1993:countNodeCrossings(ranking, left_node, right_node, sweep_direction) + + local ranks = ranking:getRanks() + local _, rank_index = lib.find(ranks, function (rank) + return rank == ranking:getRank(left_node) + end) + local other_rank_index = (sweep_direction == 'down') and rank_index-1 or rank_index+1 + + assert(ranking:getRank(left_node) == ranking:getRank(right_node)) + assert(rank_index >= 1 and rank_index <= #ranks) + + -- 0 crossings if we're at the top or bottom and are sweeping down or up + if other_rank_index < 1 or other_rank_index > #ranks then + return 0 + end + + local left_edges = {} + local right_edges = {} + + if sweep_direction == 'down' then + left_edges = left_node:getIncomingEdges() + right_edges = right_node:getIncomingEdges() + else + left_edges = left_node:getOutgoingEdges() + right_edges = right_node:getOutgoingEdges() + end + + local crossings = 0 + + local function left_neighbour_on_other_rank(edge) + local neighbour = edge:getNeighbour(left_node) + return ranking:getRank(neighbour) == ranking:getRanks()[other_rank_index] + end + + local function right_neighbour_on_other_rank(edge) + local neighbour = edge:getNeighbour(right_node) + return ranking:getRank(neighbour) == ranking:getRanks()[other_rank_index] + end + + for _,left_edge in ipairs(left_edges) do + if left_neighbour_on_other_rank(left_edge) then + local left_neighbour = left_edge:getNeighbour(left_node) + + for _,right_edge in ipairs(right_edges) do + if right_neighbour_on_other_rank(right_edge) then + local right_neighbour = right_edge:getNeighbour(right_node) + + local left_position = ranking:getRankPosition(left_neighbour) + local right_position = ranking:getRankPosition(right_neighbour) + + local neighbour_diff = right_position - left_position + + if neighbour_diff < 0 then + crossings = crossings + 1 + end + end + end + end + end + + return crossings +end + + + +function CrossingMinimizationGansnerKNV1993:orderByWeightedMedian(direction) + + local median = {} + + local function get_index(n, node) return median[node] end + local function is_fixed(n, node) return median[node] < 0 end + + if direction == 'down' then + local ranks = self.ranking:getRanks() + + for rank_index = 2, #ranks do + median = {} + local nodes = self.ranking:getNodes(ranks[rank_index]) + for _,node in ipairs(nodes) do + median[node] = self:computeMedianPosition(node, ranks[rank_index-1]) + end + + self.ranking:reorderRank(ranks[rank_index], get_index, is_fixed) + end + else + local ranks = self.ranking:getRanks() + + for rank_index = 1, #ranks-1 do + median = {} + local nodes = self.ranking:getNodes(ranks[rank_index]) + for _,node in ipairs(nodes) do + median[node] = self:computeMedianPosition(node, ranks[rank_index+1]) + end + + self.ranking:reorderRank(ranks[rank_index], get_index, is_fixed) + end + end +end + + + +function CrossingMinimizationGansnerKNV1993:computeMedianPosition(node, prev_rank) + + local positions = lib.imap( + node.edges, + function (edge) + local n = edge:getNeighbour(node) + if self.ranking:getRank(n) == prev_rank then + return self.ranking:getRankPosition(n) + end + end) + + table.sort(positions) + + local median = math.ceil(#positions / 2) + local position = -1 + + if #positions > 0 then + if #positions % 2 == 1 then + position = positions[median] + elseif #positions == 2 then + return (positions[1] + positions[2]) / 2 + else + local left = positions[median-1] - positions[1] + local right = positions[#positions] - positions[median] + position = (positions[median-1] * right + positions[median] * left) / (left + right) + end + end + + return position +end + + + +function CrossingMinimizationGansnerKNV1993:transpose(sweep_direction) + + local function transpose_rank(rank) + + local improved = false + + local nodes = self.ranking:getNodes(rank) + + for i = 1, #nodes-1 do + local v = nodes[i] + local w = nodes[i+1] + + local cn_vw = self:countNodeCrossings(self.ranking, v, w, sweep_direction) + local cn_wv = self:countNodeCrossings(self.ranking, w, v, sweep_direction) + + if cn_vw > cn_wv then + improved = true + + self:switchNodePositions(v, w) + end + end + + return improved + end + + local ranks = self.ranking:getRanks() + + local improved = false + repeat + local improved = false + + if sweep_direction == 'down' then + for rank_index = 1, #ranks-1 do + improved = transpose_rank(ranks[rank_index]) or improved + end + else + for rank_index = #ranks-1, 1, -1 do + improved = transpose_rank(ranks[rank_index]) or improved + end + end + until not improved +end + + + +function CrossingMinimizationGansnerKNV1993:switchNodePositions(left_node, right_node) + assert(self.ranking:getRank(left_node) == self.ranking:getRank(right_node)) + assert(self.ranking:getRankPosition(left_node) < self.ranking:getRankPosition(right_node)) + + local left_position = self.ranking:getRankPosition(left_node) + local right_position = self.ranking:getRankPosition(right_node) + + self.ranking:switchPositions(left_node, right_node) + + local nodes = self.ranking:getNodes(self.ranking:getRank(left_node)) +end + + + +-- done + +return CrossingMinimizationGansnerKNV1993 diff --git a/graphics/pgf/base/tex/generic/graphdrawing/lua/pgf/gd/layered/CycleRemovalBergerS1990a.lua b/graphics/pgf/base/tex/generic/graphdrawing/lua/pgf/gd/layered/CycleRemovalBergerS1990a.lua new file mode 100644 index 0000000000..76deb8adaf --- /dev/null +++ b/graphics/pgf/base/tex/generic/graphdrawing/lua/pgf/gd/layered/CycleRemovalBergerS1990a.lua @@ -0,0 +1,74 @@ +-- Copyright 2011 by Jannis Pohlmann +-- +-- This file may be distributed and/or modified +-- +-- 1. under the LaTeX Project Public License and/or +-- 2. under the GNU Public License +-- +-- See the file doc/generic/pgf/licenses/LICENSE for more information + +-- @release $Header$ + + +local CycleRemovalBergerS1990a = {} + +local lib = require("pgf.gd.lib") + + +function CycleRemovalBergerS1990a:run() + -- remember edges that were removed + local removed = {} + + -- remember edges that need to be reversed + local reverse = {} + + -- iterate over all nodes of the graph + for _,node in ipairs(self.graph.nodes) do + -- get all outgoing edges that have not been removed yet + local out_edges = lib.imap(node:getOutgoingEdges(), + function (edge) + if not removed[edge] then return edge end + end) + + -- get all incoming edges that have not been removed yet + local in_edges = lib.imap(node:getIncomingEdges(), + function (edge) + if not removed[edge] then return edge end + end) + + if #out_edges >= #in_edges then + -- we have more outgoing than incoming edges, reverse all incoming + -- edges and mark all incident edges as removed + + for _,edge in ipairs(out_edges) do + removed[edge] = true + end + for _,edge in ipairs(in_edges) do + reverse[edge] = true + removed[edge] = true + end + else + -- we have more incoming than outgoing edges, reverse all outgoing + -- edges and mark all incident edges as removed + + for _,edge in ipairs(out_edges) do + reverse[edge] = true + removed[edge] = true + end + for _,edge in ipairs(in_edges) do + removed[edge] = true + end + end + end + + -- mark edges as reversed + for edge in pairs(reverse) do + edge.reversed = true + end +end + + + +-- done + +return CycleRemovalBergerS1990a diff --git a/graphics/pgf/base/tex/generic/graphdrawing/lua/pgf/gd/layered/CycleRemovalBergerS1990b.lua b/graphics/pgf/base/tex/generic/graphdrawing/lua/pgf/gd/layered/CycleRemovalBergerS1990b.lua new file mode 100644 index 0000000000..d8f28980c8 --- /dev/null +++ b/graphics/pgf/base/tex/generic/graphdrawing/lua/pgf/gd/layered/CycleRemovalBergerS1990b.lua @@ -0,0 +1,76 @@ +-- Copyright 2011 by Jannis Pohlmann +-- +-- This file may be distributed and/or modified +-- +-- 1. under the LaTeX Project Public License and/or +-- 2. under the GNU Public License +-- +-- See the file doc/generic/pgf/licenses/LICENSE for more information + +-- @release $Header$ + + +local CycleRemovalBergerS1990b = {} + +local lib = require("pgf.gd.lib") + + +function CycleRemovalBergerS1990b:run() + -- remember edges that were removed + local removed = {} + + -- remember edges that need to be reversed + local reverse = {} + + -- iterate over all nodes of the graph + for _,j in ipairs(lib.random_permutation(#self.graph.nodes)) do + local node = self.graph.nodes[j] + -- get all outgoing edges that have not been removed yet + -- get all outgoing edges that have not been removed yet + local out_edges = lib.imap(node:getOutgoingEdges(), + function (edge) + if not removed[edge] then return edge end + end) + + -- get all incoming edges that have not been removed yet + local in_edges = lib.imap(node:getIncomingEdges(), + function (edge) + if not removed[edge] then return edge end + end) + + if #out_edges >= #in_edges then + -- we have more outgoing than incoming edges, reverse all incoming + -- edges and mark all incident edges as removed + + for _,edge in ipairs(out_edges) do + removed[edge] = true + end + for _,edge in ipairs(in_edges) do + reverse[edge] = true + removed[edge] = true + end + else + -- we have more incoming than outgoing edges, reverse all outgoing + -- edges and mark all incident edges as removed + + for _,edge in ipairs(out_edges) do + reverse[edge] = true + removed[edge] = true + end + for _,edge in ipairs(in_edges) do + removed[edge] = true + end + end + end + + -- mark edges as reversed + for edge in pairs(reverse) do + edge.reversed = true + end +end + + + +-- done + +return CycleRemovalBergerS1990b diff --git a/graphics/pgf/base/tex/generic/graphdrawing/lua/pgf/gd/layered/CycleRemovalEadesLS1993.lua b/graphics/pgf/base/tex/generic/graphdrawing/lua/pgf/gd/layered/CycleRemovalEadesLS1993.lua new file mode 100644 index 0000000000..ffb919b209 --- /dev/null +++ b/graphics/pgf/base/tex/generic/graphdrawing/lua/pgf/gd/layered/CycleRemovalEadesLS1993.lua @@ -0,0 +1,124 @@ +-- Copyright 2011 by Jannis Pohlmann +-- +-- This file may be distributed and/or modified +-- +-- 1. under the LaTeX Project Public License and/or +-- 2. under the GNU Public License +-- +-- See the file doc/generic/pgf/licenses/LICENSE for more information + +-- @release $Header$ + + + +-- Declare +local CycleRemovalEadesLS1993 = {} + +-- Import +local lib = require "pgf.gd.lib" + + +function CycleRemovalEadesLS1993:run() + local copied_graph = self.graph:copy() + + local copied_node = {} + local origin_node = {} + local copied_edge = {} + local origin_edge = {} + + local preserve = {} + + for _,edge in ipairs(self.graph.edges) do + copied_edge[edge] = edge:copy() + origin_edge[copied_edge[edge]] = edge + + for _,node in ipairs(edge.nodes) do + if copied_node[node] then + copied_edge[edge]:addNode(copied_node[node]) + else + copied_node[node] = node:copy() + origin_node[copied_node[node]] = node + + copied_graph:addNode(copied_node[node]) + copied_edge[edge]:addNode(copied_node[node]) + end + end + end + + local function node_is_sink(node) + return node:getOutDegree() == 0 + end + + local function node_is_source(node) + return node:getInDegree() == 0 + end + + local function node_is_isolated(node) + return node:getDegree() == 0 + end + + while #copied_graph.nodes > 0 do + local sink = lib.find(copied_graph.nodes, node_is_sink) + while sink do + for _,edge in ipairs(sink:getIncomingEdges()) do + preserve[edge] = true + end + copied_graph:deleteNode(sink) + sink = lib.find(copied_graph.nodes, node_is_sink) + end + + local isolated_node = lib.find(copied_graph.nodes, node_is_isolated) + while isolated_node do + copied_graph:deleteNode(isolated_node) + isolated_node = lib.find(copied_graph.nodes, node_is_isolated) + end + + local source = lib.find(copied_graph.nodes, node_is_source) + while source do + for _,edge in ipairs(source:getOutgoingEdges()) do + preserve[edge] = true + end + copied_graph:deleteNode(source) + source = lib.find(copied_graph.nodes, node_is_source) + end + + if #copied_graph.nodes > 0 then + local max_node = nil + local max_out_edges = nil + local max_in_edges = nil + + for _,node in ipairs(copied_graph.nodes) do + local out_edges = node:getOutgoingEdges() + local in_edges = node:getIncomingEdges() + + if max_node == nil or (#out_edges - #in_edges > #max_out_edges - #max_in_edges) then + max_node = node + max_out_edges = out_edges + max_in_edges = in_edges + end + end + + assert(max_node and max_out_edges and max_in_edges) + + for _,edge in ipairs(max_out_edges) do + preserve[edge] = true + copied_graph:deleteEdge(edge) + end + for _,edge in ipairs(max_in_edges) do + copied_graph:deleteEdge(edge) + end + + copied_graph:deleteNode(max_node) + end + end + + for _,edge in ipairs(self.graph.edges) do + if not preserve[copied_edge[edge]] then + edge.reversed = true + end + end +end + +-- done + +return CycleRemovalEadesLS1993
\ No newline at end of file diff --git a/graphics/pgf/base/tex/generic/graphdrawing/lua/pgf/gd/layered/CycleRemovalGansnerKNV1993.lua b/graphics/pgf/base/tex/generic/graphdrawing/lua/pgf/gd/layered/CycleRemovalGansnerKNV1993.lua new file mode 100644 index 0000000000..e52b0d38d1 --- /dev/null +++ b/graphics/pgf/base/tex/generic/graphdrawing/lua/pgf/gd/layered/CycleRemovalGansnerKNV1993.lua @@ -0,0 +1,52 @@ +-- Copyright 2011 by Jannis Pohlmann and 2012 by Till Tantau +-- +-- This file may be distributed and/or modified +-- +-- 1. under the LaTeX Project Public License and/or +-- 2. under the GNU Public License +-- +-- See the file doc/generic/pgf/licenses/LICENSE for more information + +-- @release $Header$ + + +local CycleRemovalGansnerKNV1993 = {} + + +-- Imports +local declare = require("pgf.gd.interface.InterfaceToAlgorithms").declare +local Simplifiers = require "pgf.gd.lib.Simplifiers" + + +function CycleRemovalGansnerKNV1993:run () + -- merge nonempty sets into supernodes + -- + -- ignore self-loops + -- + -- merge multiple edges into one edge each, whose weight is the sum of the + -- individual edge weights + -- + -- ignore leaf nodes that are not part of the user-defined sets (their ranks + -- are trivially determined) + -- + -- ensure that supernodes S_min and S_max are assigned first and last ranks + -- reverse in-edges of S_min + -- reverse out-edges of S_max + -- + -- ensure the supernodes S_min and S_max are are the only nodes in these ranks + -- for all nodes with indegree of 0, insert temporary edge (S_min, v) with delta=0 + -- for all nodes with outdegree of 0, insert temporary edge (v, S_max) with delta=0 + + -- classify edges as tree/forward, cross and back edges using a DFS traversal + local tree_or_forward_edges, cross_edges, back_edges = Simplifiers:classifyEdges(self.graph) + + -- reverse the back edges in order to make the graph acyclic + for _,edge in ipairs(back_edges) do + edge.reversed = true + end +end + + +-- done + +return CycleRemovalGansnerKNV1993
\ No newline at end of file diff --git a/graphics/pgf/base/tex/generic/graphdrawing/lua/pgf/gd/layered/EdgeRoutingGansnerKNV1993.lua b/graphics/pgf/base/tex/generic/graphdrawing/lua/pgf/gd/layered/EdgeRoutingGansnerKNV1993.lua new file mode 100644 index 0000000000..c451f74116 --- /dev/null +++ b/graphics/pgf/base/tex/generic/graphdrawing/lua/pgf/gd/layered/EdgeRoutingGansnerKNV1993.lua @@ -0,0 +1,22 @@ +-- Copyright 2011 by Jannis Pohlmann +-- +-- This file may be distributed and/or modified +-- +-- 1. under the LaTeX Project Public License and/or +-- 2. under the GNU Public License +-- +-- See the file doc/generic/pgf/licenses/LICENSE for more information + +-- @release $Header$ + + + +local EdgeRoutingGansnerKNV1993 = {} + + +function EdgeRoutingGansnerKNV1993:run() +end + + +-- done +return EdgeRoutingGansnerKNV1993
\ No newline at end of file diff --git a/graphics/pgf/base/tex/generic/graphdrawing/lua/pgf/gd/layered/NetworkSimplex.lua b/graphics/pgf/base/tex/generic/graphdrawing/lua/pgf/gd/layered/NetworkSimplex.lua new file mode 100644 index 0000000000..0ccf7694ca --- /dev/null +++ b/graphics/pgf/base/tex/generic/graphdrawing/lua/pgf/gd/layered/NetworkSimplex.lua @@ -0,0 +1,898 @@ +-- Copyright 2011 by Jannis Pohlmann +-- Copyright 2012 by Till Tantau +-- +-- This file may be distributed an/or modified +-- +-- 1. under the LaTeX Project Public License and/or +-- 2. under the GNU Public License +-- +-- See the file doc/generic/pgf/licenses/LICENSE for more information + +-- @release $Header$ + + + + +--- This file contains an implementation of the network simplex method +--- for node ranking and x coordinate optimization in layered drawing +--- algorithms, as proposed in +--- +--- "A Technique for Drawing Directed Graphs" +-- by Gansner, Koutsofios, North, Vo, 1993. + + +local NetworkSimplex = {} +NetworkSimplex.__index = NetworkSimplex + +-- Namespace +local layered = require "pgf.gd.layered" +layered.NetworkSimplex = NetworkSimplex + + +-- Imports +local DepthFirstSearch = require "pgf.gd.lib.DepthFirstSearch" +local Ranking = require "pgf.gd.layered.Ranking" +local Graph = require "pgf.gd.deprecated.Graph" +local lib = require "pgf.gd.lib" + + + +-- Definitions + +NetworkSimplex.BALANCE_TOP_BOTTOM = 1 +NetworkSimplex.BALANCE_LEFT_RIGHT = 2 + + +function NetworkSimplex.new(graph, balancing) + local simplex = { + graph = graph, + balancing = balancing, + } + setmetatable(simplex, NetworkSimplex) + return simplex +end + + + +function NetworkSimplex:run() + + assert (#self.graph.nodes > 0, "graph must contain at least one node") + + -- initialize the tree edge search index + self.search_index = 1 + + -- initialize internal edge parameters + self.cut_value = {} + for _,edge in ipairs(self.graph.edges) do + self.cut_value[edge] = 0 + end + + -- reset graph information needed for ranking + self.lim = {} + self.low = {} + self.parent_edge = {} + self.ranking = Ranking.new() + + if #self.graph.nodes == 1 then + self.ranking:setRank(self.graph.nodes[1], 1) + else + self:rankNodes() + end +end + + + +function NetworkSimplex:rankNodes() + -- construct feasible tree of tight edges + self:constructFeasibleTree() + + -- iteratively replace edges with negative cut values + -- with non-tree edges (chosen by minimum slack) + local leave_edge = self:findNegativeCutEdge() + while leave_edge do + local enter_edge = self:findReplacementEdge(leave_edge) + + assert(enter_edge, 'no non-tree edge to replace ' .. tostring(leave_edge) .. ' could be found') + + -- exchange leave_edge and enter_edge in the tree, updating + -- the ranks and cut values of all nodes + self:exchangeTreeEdges(leave_edge, enter_edge) + + -- find the next tree edge with a negative cut value, if + -- there are any left + leave_edge = self:findNegativeCutEdge() + end + + if self.balancing == NetworkSimplex.BALANCE_TOP_BOTTOM then + -- normalize by setting the least rank to zero + self.ranking:normalizeRanks() + + -- move nodes to feasible ranks with the least number of nodes + -- in order to avoid crowding and to improve the overall aspect + -- ratio of the drawing + self:balanceRanksTopBottom() + elseif self.balancing == NetworkSimplex.BALANCE_LEFT_RIGHT then + self:balanceRanksLeftRight() + end +end + + + +function NetworkSimplex:constructFeasibleTree() + + self:computeInitialRanking() + + -- find a maximal tree of tight edges in the graph + while self:findTightTree() < #self.graph.nodes do + + local min_slack_edge = nil + + for _,node in ipairs(self.graph.nodes) do + local out_edges = node:getOutgoingEdges() + for _,edge in ipairs(out_edges) do + if not self.tree_edge[edge] and self:isIncidentToTree(edge) then + if not min_slack_edge or self:edgeSlack(edge) < self:edgeSlack(min_slack_edge) then + min_slack_edge = edge + end + end + end + end + + if min_slack_edge then + local delta = self:edgeSlack(min_slack_edge) + + if delta > 0 then + local head = min_slack_edge:getHead() + local tail = min_slack_edge:getTail() + + if self.tree_node[head] then + delta = -delta + end + + + for _,node in ipairs(self.tree.nodes) do + local rank = self.ranking:getRank(self.orig_node[node]) + self.ranking:setRank(self.orig_node[node], rank + delta) + end + end + end + end + + self:initializeCutValues() +end + + + +function NetworkSimplex:findNegativeCutEdge() + local minimum_edge = nil + + for n=1,#self.tree.edges do + local index = self:nextSearchIndex() + + local edge = self.tree.edges[index] + + if self.cut_value[edge] < 0 then + if minimum_edge then + if self.cut_value[minimum_edge] > self.cut_value[edge] then + minimum_edge = edge + end + else + minimum_edge = edge + end + end + end + + return minimum_edge +end + + + +function NetworkSimplex:findReplacementEdge(leave_edge) + local tail = leave_edge:getTail() + local head = leave_edge:getHead() + + local v = nil + local direction = nil + + if self.lim[tail] < self.lim[head] then + v = tail + direction = 'in' + else + v = head + direction = 'out' + end + + local search_root = v + local enter_edge = nil + local slack = math.huge + + -- TODO Jannis: Get rid of this recursion: + + local function find_edge(v, direction) + + if direction == 'out' then + local out_edges = self.orig_node[v]:getOutgoingEdges() + for _,edge in ipairs(out_edges) do + local head = edge:getHead() + local tree_head = self.tree_node[head] + + assert(head and tree_head) + + if not self.tree_edge[edge] then + if not self:inTailComponentOf(tree_head, search_root) then + if self:edgeSlack(edge) < slack or not enter_edge then + enter_edge = edge + slack = self:edgeSlack(edge) + end + end + else + if self.lim[tree_head] < self.lim[v] then + find_edge(tree_head, 'out') + end + end + end + + for _,edge in ipairs(v:getIncomingEdges()) do + if slack <= 0 then + break + end + + local tail = edge:getTail() + + if self.lim[tail] < self.lim[v] then + find_edge(tail, 'out') + end + end + else + local in_edges = self.orig_node[v]:getIncomingEdges() + for _,edge in ipairs(in_edges) do + local tail = edge:getTail() + local tree_tail = self.tree_node[tail] + + assert(tail and tree_tail) + + if not self.tree_edge[edge] then + if not self:inTailComponentOf(tree_tail, search_root) then + if self:edgeSlack(edge) < slack or not enter_edge then + enter_edge = edge + slack = self:edgeSlack(edge) + end + end + else + if self.lim[tree_tail] < self.lim[v] then + find_edge(tree_tail, 'in') + end + end + end + + for _,edge in ipairs(v:getOutgoingEdges()) do + if slack <= 0 then + break + end + + local head = edge:getHead() + + if self.lim[head] < self.lim[v] then + find_edge(head, 'in') + end + end + end + end + + find_edge(v, direction) + + return enter_edge +end + + + +function NetworkSimplex:exchangeTreeEdges(leave_edge, enter_edge) + + self:rerankBeforeReplacingEdge(leave_edge, enter_edge) + + local cutval = self.cut_value[leave_edge] + local head = self.tree_node[enter_edge:getHead()] + local tail = self.tree_node[enter_edge:getTail()] + + local ancestor = self:updateCutValuesUpToCommonAncestor(tail, head, cutval, true) + local other_ancestor = self:updateCutValuesUpToCommonAncestor(head, tail, cutval, false) + + assert(ancestor == other_ancestor) + + -- remove the old edge from the tree + self:removeEdgeFromTree(leave_edge) + + -- add the new edge to the tree + local tree_edge = self:addEdgeToTree(enter_edge) + + -- set its cut value + self.cut_value[tree_edge] = -cutval + + -- update DFS search tree traversal information + self:calculateDFSRange(ancestor, self.parent_edge[ancestor], self.low[ancestor]) +end + + + +function NetworkSimplex:balanceRanksTopBottom() + + -- available ranks + local ranks = self.ranking:getRanks() + + -- node to in/out weight mappings + local in_weight = {} + local out_weight = {} + + -- node to lowest/highest possible rank mapping + local min_rank = {} + local max_rank = {} + + -- compute the in and out weights of each node + for _,node in ipairs(self.graph.nodes) do + -- assume there are no restrictions on how to rank the node + min_rank[node], max_rank[node] = ranks[1], ranks[#ranks] + + for _,edge in ipairs(node:getIncomingEdges()) do + -- accumulate the weights of all incoming edges + in_weight[node] = (in_weight[node] or 0) + edge.weight + + -- update the minimum allowed rank (which is the maximum of + -- the ranks of all parent neighbors plus the minimum level + -- separation caused by the connecting edges) + local neighbour = edge:getNeighbour(node) + local neighbour_rank = self.ranking:getRank(neighbour) + min_rank[node] = math.max(min_rank[node], neighbour_rank + edge.minimum_levels) + end + + for _,edge in ipairs(node:getOutgoingEdges()) do + -- accumulate the weights of all outgoing edges + out_weight[node] = (out_weight[node] or 0) + edge.weight + + -- update the maximum allowed rank (which is the minimum of + -- the ranks of all child neighbors minus the minimum level + -- separation caused by the connecting edges) + local neighbour = edge:getNeighbour(node) + local neighbour_rank = self.ranking:getRank(neighbour) + max_rank[node] = math.min(max_rank[node], neighbour_rank - edge.minimum_levels) + end + + -- check whether the in- and outweight is the same + if in_weight[node] == out_weight[node] then + + -- check which of the allowed ranks has the least number of nodes + local min_nodes_rank = min_rank[node] + for n = min_rank[node] + 1, max_rank[node] do + if #self.ranking:getNodes(n) < #self.ranking:getNodes(min_nodes_rank) then + min_nodes_rank = n + end + end + + -- only move the node to the rank with the least number of nodes + -- if it differs from the current rank of the node + if min_nodes_rank ~= self.ranking:getRank(node) then + self.ranking:setRank(node, min_nodes_rank) + end + + end + end +end + + + +function NetworkSimplex:balanceRanksLeftRight() + for _,edge in ipairs(self.tree.edges) do + if self.cut_value[edge] == 0 then + local other_edge = self:findReplacementEdge(edge) + if other_edge then + local delta = self:edgeSlack(other_edge) + if delta > 1 then + if self.lim[edge:getTail()] < self.lim[edge:getHead()] then + self:rerank(edge:getTail(), delta / 2) + else + self:rerank(edge:getHead(), -delta / 2) + end + end + end + end + end +end + + + +function NetworkSimplex:computeInitialRanking() + + -- queue for nodes to rank next + local queue = {} + + -- convenience functions for managing the queue + local function enqueue(node) table.insert(queue, node) end + local function dequeue() return table.remove(queue, 1) end + + -- reset the two-dimensional mapping from ranks to lists + -- of corresponding nodes + self.ranking:reset() + + -- mapping of nodes to the number of unscanned incoming edges + local remaining_edges = {} + + -- add all sinks to the queue + for _,node in ipairs(self.graph.nodes) do + local edges = node:getIncomingEdges() + + remaining_edges[node] = #edges + + if #edges == 0 then + enqueue(node) + end + end + + -- run long as there are nodes to be ranked + while #queue > 0 do + + -- fetch the next unranked node from the queue + local node = dequeue() + + -- get a list of its incoming edges + local in_edges = node:getIncomingEdges() + + -- determine the minimum possible rank for the node + local rank = 1 + for _,edge in ipairs(in_edges) do + local neighbour = edge:getNeighbour(node) + if self.ranking:getRank(neighbour) then + -- the minimum possible rank is the maximum of all neighbor ranks plus + -- the corresponding edge lengths + rank = math.max(rank, self.ranking:getRank(neighbour) + edge.minimum_levels) + end + end + + -- rank the node + self.ranking:setRank(node, rank) + + -- get a list of the node's outgoing edges + local out_edges = node:getOutgoingEdges() + + -- queue neighbors of nodes for which all incoming edges have been scanned + for _,edge in ipairs(out_edges) do + local head = edge:getHead() + remaining_edges[head] = remaining_edges[head] - 1 + if remaining_edges[head] <= 0 then + enqueue(head) + end + end + end +end + + + +function NetworkSimplex:findTightTree() + + -- TODO: Jannis: Remove the recursion below: + + local marked = {} + + local function build_tight_tree(node) + + local out_edges = node:getOutgoingEdges() + local in_edges = node:getIncomingEdges() + + local edges = lib.copy(out_edges) + for _,v in ipairs(in_edges) do + edges[#edges + 1] = v + end + + for _,edge in ipairs(edges) do + local neighbour = edge:getNeighbour(node) + if (not marked[neighbour]) and math.abs(self:edgeSlack(edge)) < 0.00001 then + self:addEdgeToTree(edge) + + for _,node in ipairs(edge.nodes) do + marked[node] = true + end + + if #self.tree.edges == #self.graph.nodes-1 then + return true + end + + if build_tight_tree(neighbour) then + return true + end + end + end + + return false + end + + for _,node in ipairs(self.graph.nodes) do + self.tree = Graph.new() + self.tree_node = {} + self.orig_node = {} + self.tree_edge = {} + self.orig_edge = {} + + build_tight_tree(node) + + if #self.tree.edges > 0 then + break + end + end + + return #self.tree.nodes +end + + + +function NetworkSimplex:edgeSlack(edge) + -- make sure this is never called with a tree edge + assert(not self.orig_edge[edge]) + + local head_rank = self.ranking:getRank(edge:getHead()) + local tail_rank = self.ranking:getRank(edge:getTail()) + local length = head_rank - tail_rank + return length - edge.minimum_levels +end + + + +function NetworkSimplex:isIncidentToTree(edge) + -- make sure this is never called with a tree edge + assert(not self.orig_edge[edge]) + + local head = edge:getHead() + local tail = edge:getTail() + + if self.tree_node[head] and not self.tree_node[tail] then + return true + elseif self.tree_node[tail] and not self.tree_node[head] then + return true + else + return false + end +end + + + +function NetworkSimplex:initializeCutValues() + self:calculateDFSRange(self.tree.nodes[1], nil, 1) + + local function init(search) + search:push({ node = self.tree.nodes[1], parent_edge = nil }) + end + + local function visit(search, data) + search:setVisited(data, true) + + local into = data.node:getIncomingEdges() + local out = data.node:getOutgoingEdges() + + for i=#into,1,-1 do + local edge = into[i] + if edge ~= data.parent_edge then + search:push({ node = edge:getTail(), parent_edge = edge }) + end + end + + for i=#out,1,-1 do + local edge = out[i] + if edge ~= data.parent_edge then + search:push({ node = edge:getHead(), parent_edge = edge }) + end + end + end + + local function complete(search, data) + if data.parent_edge then + self:updateCutValue(data.parent_edge) + end + end + + DepthFirstSearch.new(init, visit, complete):run() +end + + + +--- DFS algorithm that calculates post-order traversal indices and parent edges. +-- +-- This algorithm performs a depth-first search in a directed or undirected +-- graph. For each node it calculates the node's post-order traversal index, the +-- minimum post-order traversal index of its descendants as well as the edge by +-- which the node was reached in the depth-first traversal. +-- +function NetworkSimplex:calculateDFSRange(root, edge_from_parent, lowest) + + -- global traversal index counter + local lim = lowest + + -- start the traversal at the root node + local function init(search) + search:push({ node = root, parent_edge = edge_from_parent, low = lowest }) + end + + -- visit nodes in depth-first order + local function visit(search, data) + -- mark node as visited so we only visit it once + search:setVisited(data, true) + + -- remember the parent edge + self.parent_edge[data.node] = data.parent_edge + + -- remember the minimum traversal index for this branch of the search tree + self.low[data.node] = lim + + -- next we push all outgoing and incoming edges in reverse order + -- to simulate recursive calls + + local into = data.node:getIncomingEdges() + local out = data.node:getOutgoingEdges() + + for i=#into,1,-1 do + local edge = into[i] + if edge ~= data.parent_edge then + search:push({ node = edge:getTail(), parent_edge = edge }) + end + end + + for i=#out,1,-1 do + local edge = out[i] + if edge ~= data.parent_edge then + search:push({ node = edge:getHead(), parent_edge = edge }) + end + end + end + + -- when completing a node, store its own traversal index + local function complete(search, data) + self.lim[data.node] = lim + lim = lim + 1 + end + + -- kick off the depth-first search + DepthFirstSearch.new(init, visit, complete):run() + + local lim_lookup = {} + local min_lim = math.huge + local max_lim = -math.huge + for _,node in ipairs(self.tree.nodes) do + assert(self.lim[node]) + assert(self.low[node]) + assert(not lim_lookup[self.lim[node]]) + lim_lookup[self.lim[node]] = true + min_lim = math.min(min_lim, self.lim[node]) + max_lim = math.max(max_lim, self.lim[node]) + end + for n = min_lim, max_lim do + assert(lim_lookup[n] == true) + end +end + + + +function NetworkSimplex:updateCutValue(tree_edge) + + local v = nil + if self.parent_edge[tree_edge:getTail()] == tree_edge then + v = tree_edge:getTail() + dir = 1 + else + v = tree_edge:getHead() + dir = -1 + end + + local sum = 0 + + local out_edges = self.orig_node[v]:getOutgoingEdges() + local in_edges = self.orig_node[v]:getIncomingEdges() + local edges = lib.copy(out_edges) + for _,v in ipairs(in_edges) do + edges[#edges + 1] = v + end + + for _,edge in ipairs(edges) do + local other = edge:getNeighbour(self.orig_node[v]) + + local f = 0 + local rv = 0 + + if not self:inTailComponentOf(self.tree_node[other], v) then + f = 1 + rv = edge.weight + else + f = 0 + + if self.tree_edge[edge] then + rv = self.cut_value[self.tree_edge[edge]] + else + rv = 0 + end + + rv = rv - edge.weight + end + + local d = 0 + + if dir > 0 then + if edge:isHead(self.orig_node[v]) then + d = 1 + else + d = -1 + end + else + if edge:isTail(self.orig_node[v]) then + d = 1 + else + d = -1 + end + end + + if f > 0 then + d = -d + end + + if d < 0 then + rv = -rv + end + + sum = sum + rv + end + + self.cut_value[tree_edge] = sum +end + + + +function NetworkSimplex:inTailComponentOf(node, v) + return (self.low[v] <= self.lim[node]) and (self.lim[node] <= self.lim[v]) +end + + + +function NetworkSimplex:nextSearchIndex() + local index = 1 + + -- avoid tree edge index out of bounds by resetting the search index + -- as soon as it leaves the range of edge indices in the tree + if self.search_index > #self.tree.edges then + self.search_index = 1 + index = 1 + else + index = self.search_index + self.search_index = self.search_index + 1 + end + + return index +end + + + +function NetworkSimplex:rerank(node, delta) + local function init(search) + search:push({ node = node, delta = delta }) + end + + local function visit(search, data) + search:setVisited(data, true) + + local orig_node = self.orig_node[data.node] + self.ranking:setRank(orig_node, self.ranking:getRank(orig_node) - data.delta) + + local into = data.node:getIncomingEdges() + local out = data.node:getOutgoingEdges() + + for i=#into,1,-1 do + local edge = into[i] + if edge ~= self.parent_edge[data.node] then + search:push({ node = edge:getTail(), delta = data.delta }) + end + end + + for i=#out,1,-1 do + local edge = out[i] + if edge ~= self.parent_edge[data.node] then + search:push({ node = edge:getHead(), delta = data.delta }) + end + end + end + + DepthFirstSearch.new(init, visit):run() +end + + + +function NetworkSimplex:rerankBeforeReplacingEdge(leave_edge, enter_edge) + local delta = self:edgeSlack(enter_edge) + + if delta > 0 then + local tail = leave_edge:getTail() + + if #tail.edges == 1 then + self:rerank(tail, delta) + else + local head = leave_edge:getHead() + + if #head.edges == 1 then + self:rerank(head, -delta) + else + if self.lim[tail] < self.lim[head] then + self:rerank(tail, delta) + else + self:rerank(head, -delta) + end + end + end + end +end + + + +function NetworkSimplex:updateCutValuesUpToCommonAncestor(v, w, cutval, dir) + + while not self:inTailComponentOf(w, v) do + local edge = self.parent_edge[v] + + if edge:isTail(v) then + d = dir + else + d = not dir + end + + if d then + self.cut_value[edge] = self.cut_value[edge] + cutval + else + self.cut_value[edge] = self.cut_value[edge] - cutval + end + + if self.lim[edge:getTail()] > self.lim[edge:getHead()] then + v = edge:getTail() + else + v = edge:getHead() + end + end + + return v +end + + + +function NetworkSimplex:addEdgeToTree(edge) + assert(not self.tree_edge[edge]) + + -- create the new tree edge + local tree_edge = edge:copy() + self.orig_edge[tree_edge] = edge + self.tree_edge[edge] = tree_edge + + -- create tree nodes if necessary + for _,node in ipairs(edge.nodes) do + local tree_node + + if self.tree_node[node] then + tree_node = self.tree_node[node] + else + tree_node = node:copy() + self.orig_node[tree_node] = node + self.tree_node[node] = tree_node + end + + self.tree:addNode(tree_node) + tree_edge:addNode(tree_node) + end + + self.tree:addEdge(tree_edge) + + return tree_edge +end + + + +function NetworkSimplex:removeEdgeFromTree(edge) + self.tree:deleteEdge(edge) + self.tree_edge[self.orig_edge[edge]] = nil + self.orig_edge[edge] = nil +end + + + + +-- Done + +return NetworkSimplex
\ No newline at end of file diff --git a/graphics/pgf/base/tex/generic/graphdrawing/lua/pgf/gd/layered/NodePositioningGansnerKNV1993.lua b/graphics/pgf/base/tex/generic/graphdrawing/lua/pgf/gd/layered/NodePositioningGansnerKNV1993.lua new file mode 100644 index 0000000000..3e5a0f8f87 --- /dev/null +++ b/graphics/pgf/base/tex/generic/graphdrawing/lua/pgf/gd/layered/NodePositioningGansnerKNV1993.lua @@ -0,0 +1,154 @@ +-- Copyright 2011 by Jannis Pohlmann +-- +-- This file may be distributed and/or modified +-- +-- 1. under the LaTeX Project Public License and/or +-- 2. under the GNU Public License +-- +-- See the file doc/generic/pgf/licenses/LICENSE for more information + +-- @release $Header$ + + + +local NodePositioningGansnerKNV1993 = {} + + +-- Imports + +local layered = require "pgf.gd.layered" + +local Graph = require "pgf.gd.deprecated.Graph" +local Edge = require "pgf.gd.deprecated.Edge" +local Node = require "pgf.gd.deprecated.Node" + +local NetworkSimplex = require "pgf.gd.layered.NetworkSimplex" +local Storage = require "pgf.gd.lib.Storage" + + +function NodePositioningGansnerKNV1993:run() + local auxiliary_graph = self:constructAuxiliaryGraph() + + local simplex = NetworkSimplex.new(auxiliary_graph, NetworkSimplex.BALANCE_LEFT_RIGHT) + simplex:run() + local x_ranking = simplex.ranking + + local layers = Storage.new() + + local ranks = self.ranking:getRanks() + for _,rank in ipairs(ranks) do + local nodes = self.ranking:getNodes(rank) + for _,node in ipairs(nodes) do + node.pos.x = x_ranking:getRank(node.aux_node) + layers[node.orig_vertex] = rank + end + end + + layered.arrange_layers_by_baselines(layers, self.main_algorithm.adjusted_bb, self.main_algorithm.ugraph) + + -- Copy back + for _,rank in ipairs(ranks) do + local nodes = self.ranking:getNodes(rank) + for _,node in ipairs(nodes) do + node.pos.y = node.orig_vertex.pos.y + end + end +end + + + + +function NodePositioningGansnerKNV1993:constructAuxiliaryGraph() + + local aux_graph = Graph.new() + + local edge_node = {} + + for _,node in ipairs(self.graph.nodes) do + local copy = Node.new{ + name = node.name, + orig_node = node, + } + node.aux_node = copy + aux_graph:addNode(copy) + end + + for i=#self.graph.edges,1,-1 do + local edge = self.graph.edges[i] + local node = Node.new{ + name = '{' .. tostring(edge) .. '}', + } + + aux_graph:addNode(node) + + node.orig_edge = edge + edge_node[edge] = node + + local head = edge:getHead() + local tail = edge:getTail() + + local tail_edge = Edge.new{ + direction = Edge.RIGHT, + minimum_levels = 0, + weight = edge.weight * self:getOmega(edge), + } + tail_edge:addNode(node) + tail_edge:addNode(tail.aux_node) + aux_graph:addEdge(tail_edge) + + local head_edge = Edge.new{ + direction = Edge.RIGHT, + minimum_levels = 0, + weight = edge.weight * self:getOmega(edge), + } + head_edge:addNode(node) + head_edge:addNode(head.aux_node) + aux_graph:addEdge(head_edge) + end + + local ranks = self.ranking:getRanks() + for _,rank in ipairs(ranks) do + local nodes = self.ranking:getNodes(rank) + for n = 1, #nodes-1 do + local v = nodes[n] + local w = nodes[n+1] + + local separator_edge = Edge.new{ + direction = Edge.RIGHT, + minimum_levels = self:getDesiredHorizontalDistance(v, w), + weight = 0, + } + separator_edge:addNode(v.aux_node) + separator_edge:addNode(w.aux_node) + aux_graph:addEdge(separator_edge) + end + end + + return aux_graph +end + + + +function NodePositioningGansnerKNV1993:getOmega(edge) + local node1 = edge.nodes[1] + local node2 = edge.nodes[2] + + if (node1.kind == "dummy") and (node2.kind == "dummy") then + return 8 + elseif (node1.kind == "dummy") or (node2.kind == "dummy") then + return 2 + else + return 1 + end +end + + + +function NodePositioningGansnerKNV1993:getDesiredHorizontalDistance(v, w) + return layered.ideal_sibling_distance(self.main_algorithm.adjusted_bb, self.graph.orig_digraph, v.orig_vertex, w.orig_vertex) +end + + +-- done + +return NodePositioningGansnerKNV1993
\ No newline at end of file diff --git a/graphics/pgf/base/tex/generic/graphdrawing/lua/pgf/gd/layered/NodeRankingGansnerKNV1993.lua b/graphics/pgf/base/tex/generic/graphdrawing/lua/pgf/gd/layered/NodeRankingGansnerKNV1993.lua new file mode 100644 index 0000000000..d6765ae2f0 --- /dev/null +++ b/graphics/pgf/base/tex/generic/graphdrawing/lua/pgf/gd/layered/NodeRankingGansnerKNV1993.lua @@ -0,0 +1,159 @@ +-- Copyright 2011 by Jannis Pohlmann +-- +-- This file may be distributed and/or modified +-- +-- 1. under the LaTeX Project Public License and/or +-- 2. under the GNU Public License +-- +-- See the file doc/generic/pgf/licenses/LICENSE for more information + +-- @release $Header$ + + +local NodeRankingGansnerKNV1993 = {} + + +-- Imports + +local Edge = require "pgf.gd.deprecated.Edge" +local Node = require "pgf.gd.deprecated.Node" + +local NetworkSimplex = require "pgf.gd.layered.NetworkSimplex" + + + +function NodeRankingGansnerKNV1993:run() + + local simplex = NetworkSimplex.new(self.graph, NetworkSimplex.BALANCE_TOP_BOTTOM) + simplex:run() + self.ranking = simplex.ranking + + return simplex.ranking +end + + + +function NodeRankingGansnerKNV1993:mergeClusters() + + self.cluster_nodes = {} + self.cluster_node = {} + self.cluster_edges = {} + + self.original_nodes = {} + self.original_edges = {} + + for _,cluster in ipairs(self.graph.clusters) do + + local cluster_node = Node.new{ + name = 'cluster@' .. cluster.name, + } + table.insert(self.cluster_nodes, cluster_node) + + for _,node in ipairs(cluster.nodes) do + self.cluster_node[node] = cluster_node + table.insert(self.original_nodes, node) + end + + self.graph:addNode(cluster_node) + end + + for _,edge in ipairs(self.graph.edges) do + local tail = edge:getTail() + local head = edge:getHead() + + if self.cluster_node[tail] or self.cluster_node[head] then + table.insert(self.original_edges, edge) + + local cluster_edge = Edge.new{ + direction = Edge.RIGHT, + weight = edge.weight, + minimum_levels = edge.minimum_levels, + } + table.insert(self.cluster_edges, cluster_edge) + + if self.cluster_node[tail] then + cluster_edge:addNode(self.cluster_node[tail]) + else + cluster_edge:addNode(tail) + end + + if self.cluster_node[head] then + cluster_edge:addNode(self.cluster_node[head]) + else + cluster_edge:addNode(head) + end + + end + end + + for _,edge in ipairs(self.cluster_edges) do + self.graph:addEdge(edge) + end + + for _,edge in ipairs(self.original_edges) do + self.graph:deleteEdge(edge) + end + + for _,node in ipairs(self.original_nodes) do + self.graph:deleteNode(node) + end +end + + + +function NodeRankingGansnerKNV1993:createClusterEdges() + for n = 1, #self.cluster_nodes-1 do + local first_cluster = self.cluster_nodes[n] + local second_cluster = self.cluster_nodes[n+1] + + local edge = Edge.new{ + direction = Edge.RIGHT, + weight = 1, + minimum_levels = 1, + } + + edge:addNode(first_cluster) + edge:addNode(second_cluster) + + self.graph:addEdge(edge) + + table.insert(self.cluster_edges, edge) + end +end + + + +function NodeRankingGansnerKNV1993:removeClusterEdges() +end + + + +function NodeRankingGansnerKNV1993:expandClusters() + + for _,node in ipairs(self.original_nodes) do + assert(self.ranking:getRank(self.cluster_node[node])) + self.ranking:setRank(node, self.ranking:getRank(self.cluster_node[node])) + self.graph:addNode(node) + end + + for _,edge in ipairs(self.original_edges) do + for _,node in ipairs(edge.nodes) do + node:addEdge(edge) + end + self.graph:addEdge(edge) + end + + for _,node in ipairs(self.cluster_nodes) do + self.ranking:setRank(node, nil) + self.graph:deleteNode(node) + end + + for _,edge in ipairs(self.cluster_edges) do + self.graph:deleteEdge(edge) + end +end + + +-- done + +return NodeRankingGansnerKNV1993
\ No newline at end of file diff --git a/graphics/pgf/base/tex/generic/graphdrawing/lua/pgf/gd/layered/NodeRankingMinimumHeight.lua b/graphics/pgf/base/tex/generic/graphdrawing/lua/pgf/gd/layered/NodeRankingMinimumHeight.lua new file mode 100644 index 0000000000..644b99c28a --- /dev/null +++ b/graphics/pgf/base/tex/generic/graphdrawing/lua/pgf/gd/layered/NodeRankingMinimumHeight.lua @@ -0,0 +1,47 @@ +-- Copyright 2011 by Jannis Pohlmann +-- +-- This file may be distributed and/or modified +-- +-- 1. under the LaTeX Project Public License and/or +-- 2. under the GNU Public License +-- +-- See the file doc/generic/pgf/licenses/LICENSE for more information + +-- @release $Header$ + + +local NodeRankingMinimumHeight = {} + +-- Imports + +local Ranking = require "pgf.gd.layered.Ranking" +local Iterators = require "pgf.gd.deprecated.Iterators" + + +function NodeRankingMinimumHeight:run() + local ranking = Ranking.new() + + for node in Iterators.topologicallySorted(self.graph) do + local edges = node:getIncomingEdges() + + if #edges == 0 then + ranking:setRank(node, 1) + else + local max_rank = -math.huge + for _,edge in ipairs(edges) do + max_rank = math.max(max_rank, ranking:getRank(edge:getNeighbour(node))) + end + + assert(max_rank >= 1) + + ranking:setRank(node, max_rank + 1) + end + end + + return ranking +end + + +-- done + +return NodeRankingMinimumHeight diff --git a/graphics/pgf/base/tex/generic/graphdrawing/lua/pgf/gd/layered/Ranking.lua b/graphics/pgf/base/tex/generic/graphdrawing/lua/pgf/gd/layered/Ranking.lua new file mode 100644 index 0000000000..6ed63b0249 --- /dev/null +++ b/graphics/pgf/base/tex/generic/graphdrawing/lua/pgf/gd/layered/Ranking.lua @@ -0,0 +1,291 @@ +-- Copyright 2011 by Jannis Pohlmann +-- Copyright 2012 by Till Tantau +-- +-- This file may be distributed an/or modified +-- +-- 1. under the LaTeX Project Public License and/or +-- 2. under the GNU Public License +-- +-- See the file doc/generic/pgf/licenses/LICENSE for more information + +-- @release $Header$ + + + +--- The Ranking class is used by the Sugiyama algorithm to compute an +-- ordering on the nodes of a layer + +local Ranking = {} +Ranking.__index = Ranking + +-- Namespace +local layered = require "pgf.gd.layered" +layered.Ranking = Ranking + + +local lib = require "pgf.gd.lib" + + +-- TODO Jannis: document! + + +function Ranking.new() + local ranking = { + rank_to_nodes = {}, + node_to_rank = {}, + position_in_rank = {}, + } + setmetatable(ranking, Ranking) + return ranking +end + + + +function Ranking:copy() + local copied_ranking = Ranking.new() + + -- copy rank to nodes mapping + for rank, nodes in pairs(self.rank_to_nodes) do + copied_ranking.rank_to_nodes[rank] = lib.copy(self.rank_to_nodes[rank]) + end + + -- copy node to rank mapping + copied_ranking.node_to_rank = lib.copy(self.node_to_rank) + + -- copy node to position in rank mapping + copied_ranking.position_in_rank = lib.copy(self.position_in_rank) + + return copied_ranking +end + + + +function Ranking:reset() + self.rank_to_nodes = {} + self.node_to_rank = {} + self.position_in_rank = {} +end + + + +function Ranking:getRanks() + local ranks = {} + for rank, nodes in pairs(self.rank_to_nodes) do + table.insert(ranks, rank) + end + table.sort(ranks) + return ranks +end + + + +function Ranking:getRankSize(rank) + if self.rank_to_nodes[rank] then + return #self.rank_to_nodes[rank] + else + return 0 + end +end + + + +function Ranking:getNodeInfo(node) + return self:getRank(node), self:getRankPosition(node) +end + + + +function Ranking:getNodes(rank) + return self.rank_to_nodes[rank] or {} +end + + + +function Ranking:getRank(node) + return self.node_to_rank[node] +end + + + +function Ranking:setRank(node, new_rank) + local rank, pos = self:getNodeInfo(node) + + if rank == new_rank then + return + end + + if rank then + for n = pos+1, #self.rank_to_nodes[rank] do + local other_node = self.rank_to_nodes[rank][n] + self.position_in_rank[other_node] = self.position_in_rank[other_node]-1 + end + + table.remove(self.rank_to_nodes[rank], pos) + self.node_to_rank[node] = nil + self.position_in_rank[node] = nil + + if #self.rank_to_nodes[rank] == 0 then + self.rank_to_nodes[rank] = nil + end + end + + if new_rank then + self.rank_to_nodes[new_rank] = self.rank_to_nodes[new_rank] or {} + table.insert(self.rank_to_nodes[new_rank], node) + self.node_to_rank[node] = new_rank + self.position_in_rank[node] = #self.rank_to_nodes[new_rank] + end +end + + + +function Ranking:getRankPosition(node) + return self.position_in_rank[node] +end + + + +function Ranking:setRankPosition(node, new_pos) + local rank, pos = self:getNodeInfo(node) + + assert((rank and pos) or ((not rank) and (not pos))) + + if pos == new_pos then + return + end + + if rank and pos then + for n = pos+1, #self.rank_to_nodes[rank] do + local other_node = self.rank_to_nodes[rank][n] + self.position_in_rank[other_node] = self.position_in_rank[other_node]-1 + end + + table.remove(self.rank_to_nodes[rank], pos) + self.node_to_rank[node] = nil + self.position_in_rank[node] = nil + end + + if new_pos then + self.rank_to_nodes[rank] = self.rank_to_nodes[rank] or {} + + for n = new_pos+1, #self.rank_to_nodes[rank] do + local other_node = self.rank_to_nodes[rank][new_pos] + self.position_in_rank[other_node] = self.position_in_rank[other_node]+1 + end + + table.insert(self.rank_to_nodes[rank], node) + self.node_to_rank[node] = rank + self.position_in_rank[node] = new_pos + end +end + + + +function Ranking:normalizeRanks() + + -- get the current ranks + local ranks = self:getRanks() + + local min_rank = ranks[1] + local max_rank = ranks[#ranks] + + -- clear ranks + self.rank_to_nodes = {} + + -- iterate over all nodes and rerank them manually + for node in pairs(self.position_in_rank) do + local rank, pos = self:getNodeInfo(node) + local new_rank = rank - (min_rank - 1) + + self.rank_to_nodes[new_rank] = self.rank_to_nodes[new_rank] or {} + self.rank_to_nodes[new_rank][pos] = node + + self.node_to_rank[node] = new_rank + end +end + + + +function Ranking:switchPositions(left_node, right_node) + local left_rank = self.node_to_rank[left_node] + local right_rank = self.node_to_rank[right_node] + + assert(left_rank == right_rank, 'only positions of nodes in the same rank can be switched') + + local left_pos = self.position_in_rank[left_node] + local right_pos = self.position_in_rank[right_node] + + self.rank_to_nodes[left_rank][left_pos] = right_node + self.rank_to_nodes[left_rank][right_pos] = left_node + + self.position_in_rank[left_node] = right_pos + self.position_in_rank[right_node] = left_pos +end + + + +function Ranking:reorderRank(rank, get_index_func, is_fixed_func) + self:reorderTable(self.rank_to_nodes[rank], get_index_func, is_fixed_func) + + for n = 1, #self.rank_to_nodes[rank] do + self.position_in_rank[self.rank_to_nodes[rank][n]] = n + end +end + + + +function Ranking:reorderTable(input, get_index_func, is_fixed_func) + -- collect all allowed indices + local allowed_indices = {} + for n = 1, #input do + if not is_fixed_func(n, input[n]) then + table.insert(allowed_indices, n) + end + end + + -- collect all desired indices; for each of these desired indices, + -- remember by which element it was requested + local desired_to_real_indices = {} + local sort_indices = {} + for n = 1, #input do + if not is_fixed_func(n, input[n]) then + local index = get_index_func(n, input[n]) + if not desired_to_real_indices[index] then + desired_to_real_indices[index] = {} + table.insert(sort_indices, index) + end + table.insert(desired_to_real_indices[index], n) + end + end + + -- sort the desired indices + table.sort(sort_indices) + + -- compute the final indices by counting the final indices generated + -- prior to the current one and by mapping this number to the allowed + -- index with the same number + local final_indices = {} + local n = 1 + for _,index in ipairs(sort_indices) do + local real_indices = desired_to_real_indices[index] + for _,real_index in ipairs(real_indices) do + final_indices[real_index] = allowed_indices[n] + n = n + 1 + end + end + + -- flat-copy the input table so that we can still access the elements + -- using their real index while overwriting the input table in-place + local input_copy = lib.copy(input) + + -- move flexible elements to their final indices + for old_index, new_index in pairs(final_indices) do + input[new_index] = input_copy[old_index] + end +end + + + +-- Done + +return Ranking
\ No newline at end of file diff --git a/graphics/pgf/base/tex/generic/graphdrawing/lua/pgf/gd/layered/Sugiyama.lua b/graphics/pgf/base/tex/generic/graphdrawing/lua/pgf/gd/layered/Sugiyama.lua new file mode 100644 index 0000000000..c91818dc30 --- /dev/null +++ b/graphics/pgf/base/tex/generic/graphdrawing/lua/pgf/gd/layered/Sugiyama.lua @@ -0,0 +1,476 @@ +-- Copyright 2011 by Jannis Pohlmann, 2012 by Till Tantau +-- +-- This file may be distributed and/or modified +-- +-- 1. under the LaTeX Project Public License and/or +-- 2. under the GNU Public License +-- +-- See the file doc/generic/pgf/licenses/LICENSE for more information + +-- @release $Header$ + + + +--- +-- @section subsection {The Modular Sugiyama Method} +-- +-- @end + +local Sugiyama = {} + +-- Namespace +require("pgf.gd.layered").Sugiyama = Sugiyama + +-- Imports +local layered = require "pgf.gd.layered" +local declare = require("pgf.gd.interface.InterfaceToAlgorithms").declare + +local Ranking = require "pgf.gd.layered.Ranking" +local Simplifiers = require "pgf.gd.lib.Simplifiers" + +-- Deprecated stuff. Need to get rid of it! +local Edge = require "pgf.gd.deprecated.Edge" +local Node = require "pgf.gd.deprecated.Node" + +local Iterators = require "pgf.gd.deprecated.Iterators" +local Vector = require "pgf.gd.deprecated.Vector" + + + +--- + +declare { + key = "layered layout", + algorithm = Sugiyama, + + preconditions = { + connected = true, + loop_free = true, + }, + + postconditions = { + upward_oriented = true + }, + + old_graph_model = true, + + summary = [[" + The |layered layout| is the key used to select the modular Sugiyama + layout algorithm. + "]], + documentation = [[" + This algorithm consists of five consecutive steps, each of which can be + configured independently of the other ones (how this is done is + explained later in this section). Naturally, the ``best'' heuristics + are selected by default, so there is typically no need to change the + settings, but what is the ``best'' method for one graph need not be + the best one for another graph. + + As can be seen in the first example, the algorithm will not only + position the nodes of a graph, but will also perform an edge + routing. This will look visually quite pleasing if you add the + |rounded corners| option: + "]], + examples = {[[" + \tikz \graph [layered layout, sibling distance=7mm] + { + a -> { + b, + c -> { d, e, f } + } -> + h -> + a + }; + "]],[[" + \tikz [rounded corners] \graph [layered layout, sibling distance=7mm] + { + a -> { + b, + c -> { d, e, f } + } -> + h -> + a + }; + "]] + } +} + +--- + +declare { + key = "minimum layers", + type = "number", + initial = "1", + + summary = [[" + The minimum number of levels that an edge must span. It is a bit of + the opposite of the |weight| parameter: While a large |weight| + causes an edge to become shorter, a larger |minimum layers| value + causes an edge to be longer. + "]], + examples = [[" + \tikz \graph [layered layout] { + a -- {b [> minimum layers=3], c, d} -- e -- a; + }; + "]] +} + + +--- + +declare { + key = "same layer", + layer = 0, + + summary = [[" + The |same layer| collection allows you to enforce that several nodes + a on the same layer of a layered layout (this option is also known + as |same rank|). You use it like this: + "]], + examples = {[[" + \tikz \graph [layered layout] { + a -- b -- c -- d -- e; + + { [same layer] a, b }; + { [same layer] d, e }; + }; + "]],[[" + \tikz [rounded corners] \graph [layered layout] { + 1972 -> 1976 -> 1978 -> 1980 -> 1982 -> 1984 -> 1986 -> 1988 -> 1990 -> future; + + { [same layer] 1972, Thompson }; + { [same layer] 1976, Mashey, Bourne }, + { [same layer] 1978, Formshell, csh }, + { [same layer] 1980, esh, vsh }, + { [same layer] 1982, ksh, "System-V" }, + { [same layer] 1984, v9sh, tcsh }, + { [same layer] 1986, "ksh-i" }, + { [same layer] 1988, KornShell ,Perl, rc }, + { [same layer] 1990, tcl, Bash }, + { [same layer] "future", POSIX, "ksh-POSIX" }, + + Thompson -> { Mashey, Bourne, csh -> tcsh}, + Bourne -> { ksh, esh, vsh, "System-V", v9sh -> rc, Bash}, + { "ksh-i", KornShell } -> Bash, + { esh, vsh, Formshell, csh } -> ksh, + { KornShell, "System-V" } -> POSIX, + ksh -> "ksh-i" -> KornShell -> "ksh-POSIX", + Bourne -> Formshell, + + { [edge={draw=none}] + Bash -> tcl, + KornShell -> Perl + } + }; + "]] + } +} + + + +-- Implementation + +function Sugiyama:run() + if #self.graph.nodes <= 1 then + return + end + + local options = self.digraph.options + + local cycle_removal_algorithm_class = options.algorithm_phases['cycle removal'] + local node_ranking_algorithm_class = options.algorithm_phases['node ranking'] + local crossing_minimization_algorithm_class = options.algorithm_phases['crossing minimization'] + local node_positioning_algorithm_class = options.algorithm_phases['node positioning'] + local edge_routing_algorithm_class = options.algorithm_phases['layer edge routing'] + + self:preprocess() + + -- Helper function for collapsing multiedges + local function collapse (m,e) + m.weight = (m.weight or 0) + e.weight + m.minimum_levels = math.max((m.minimum_levels or 0), e.minimum_levels) + end + + -- Rank using cluster + + -- Create a subalgorithm object. Needed so that removed loops + -- are not stored on top of removed loops from main call. + local cluster_subalgorithm = { graph = self.graph } + self.graph:registerAlgorithm(cluster_subalgorithm) + + self:mergeClusters() + + Simplifiers:removeLoopsOldModel(cluster_subalgorithm) + Simplifiers:collapseMultiedgesOldModel(cluster_subalgorithm, collapse) + + cycle_removal_algorithm_class.new { main_algorithm = self, graph = self.graph }:run() + self.ranking = node_ranking_algorithm_class.new{ main_algorithm = self, graph = self.graph }:run() + self:restoreCycles() + + Simplifiers:expandMultiedgesOldModel(cluster_subalgorithm) + Simplifiers:restoreLoopsOldModel(cluster_subalgorithm) + + self:expandClusters() + + -- Now do actual computation + Simplifiers:collapseMultiedgesOldModel(cluster_subalgorithm, collapse) + cycle_removal_algorithm_class.new{ main_algorithm = self, graph = self.graph }:run() + self:insertDummyNodes() + + -- Main algorithm + crossing_minimization_algorithm_class.new{ + main_algorithm = self, + graph = self.graph, + ranking = self.ranking + }:run() + node_positioning_algorithm_class.new{ + main_algorithm = self, + graph = self.graph, + ranking = self.ranking + }:run() + + -- Cleanup + self:removeDummyNodes() + Simplifiers:expandMultiedgesOldModel(cluster_subalgorithm) + edge_routing_algorithm_class.new{ main_algorithm = self, graph = self.graph }:run() + self:restoreCycles() + +end + + + +function Sugiyama:preprocess() + -- initialize edge parameters + for _,edge in ipairs(self.graph.edges) do + -- read edge parameters + edge.weight = edge:getOption('weight') + edge.minimum_levels = edge:getOption('minimum layers') + + -- validate edge parameters + assert(edge.minimum_levels >= 0, 'the edge ' .. tostring(edge) .. ' needs to have a minimum layers value greater than or equal to 0') + end +end + + + +function Sugiyama:insertDummyNodes() + -- enumerate dummy nodes using a globally unique numeric ID + local dummy_id = 1 + + -- keep track of the original edges removed + self.original_edges = {} + + -- keep track of dummy nodes introduced + self.dummy_nodes = {} + + for node in Iterators.topologicallySorted(self.graph) do + local in_edges = node:getIncomingEdges() + + for _,edge in ipairs (in_edges) do + local neighbour = edge:getNeighbour(node) + local dist = self.ranking:getRank(node) - self.ranking:getRank(neighbour) + + if dist > 1 then + local dummies = {} + + for i=1,dist-1 do + local rank = self.ranking:getRank(neighbour) + i + + local dummy = Node.new{ + pos = Vector.new(), + name = 'dummy@' .. neighbour.name .. '@to@' .. node.name .. '@at@' .. rank, + kind = "dummy", + orig_vertex = pgf.gd.model.Vertex.new{} + } + + dummy_id = dummy_id + 1 + + self.graph:addNode(dummy) + self.ugraph:add {dummy.orig_vertex} + + self.ranking:setRank(dummy, rank) + + table.insert(self.dummy_nodes, dummy) + table.insert(edge.bend_nodes, dummy) + + table.insert(dummies, dummy) + end + + table.insert(dummies, 1, neighbour) + table.insert(dummies, #dummies+1, node) + + for i = 2, #dummies do + local source = dummies[i-1] + local target = dummies[i] + + local dummy_edge = Edge.new{ + direction = Edge.RIGHT, + reversed = false, + weight = edge.weight, -- TODO or should we divide the weight of the original edge by the number of virtual edges? + } + + dummy_edge:addNode(source) + dummy_edge:addNode(target) + + self.graph:addEdge(dummy_edge) + end + + table.insert(self.original_edges, edge) + end + end + end + + for _,edge in ipairs(self.original_edges) do + self.graph:deleteEdge(edge) + end +end + + + +function Sugiyama:removeDummyNodes() + -- delete dummy nodes + for _,node in ipairs(self.dummy_nodes) do + self.graph:deleteNode(node) + end + + -- add original edge again + for _,edge in ipairs(self.original_edges) do + -- add edge to the graph + self.graph:addEdge(edge) + + -- add edge to the nodes + for _,node in ipairs(edge.nodes) do + node:addEdge(edge) + end + + -- convert bend nodes to bend points for TikZ + for _,bend_node in ipairs(edge.bend_nodes) do + local point = bend_node.pos:copy() + table.insert(edge.bend_points, point) + end + + if edge.reversed then + local bp = edge.bend_points + for i=1,#bp/2 do + local j = #bp + 1 - i + bp[i], bp[j] = bp[j], bp[i] + end + end + + -- clear the list of bend nodes + edge.bend_nodes = {} + end +end + + + +function Sugiyama:mergeClusters() + + self.cluster_nodes = {} + self.cluster_node = {} + self.cluster_edges = {} + self.cluster_original_edges = {} + self.original_nodes = {} + + for _,cluster in ipairs(self.graph.clusters) do + + local cluster_node = cluster.nodes[1] + table.insert(self.cluster_nodes, cluster_node) + + for n = 2, #cluster.nodes do + local other_node = cluster.nodes[n] + self.cluster_node[other_node] = cluster_node + table.insert(self.original_nodes, other_node) + end + end + + for _,edge in ipairs(self.graph.edges) do + local tail = edge:getTail() + local head = edge:getHead() + + if self.cluster_node[tail] or self.cluster_node[head] then + local cluster_edge = Edge.new{ + direction = Edge.RIGHT, + weight = edge.weight, + minimum_levels = edge.minimum_levels, + } + + if self.cluster_node[tail] then + cluster_edge:addNode(self.cluster_node[tail]) + else + cluster_edge:addNode(tail) + end + + if self.cluster_node[head] then + cluster_edge:addNode(self.cluster_node[head]) + else + cluster_edge:addNode(head) + end + + table.insert(self.cluster_edges, cluster_edge) + table.insert(self.cluster_original_edges, edge) + end + end + + for n = 1, #self.cluster_nodes-1 do + local first_node = self.cluster_nodes[n] + local second_node = self.cluster_nodes[n+1] + + local edge = Edge.new{ + direction = Edge.RIGHT, + weight = 1, + minimum_levels = 1, + } + + edge:addNode(first_node) + edge:addNode(second_node) + + table.insert(self.cluster_edges, edge) + end + + for _,node in ipairs(self.original_nodes) do + self.graph:deleteNode(node) + end + for _,edge in ipairs(self.cluster_edges) do + self.graph:addEdge(edge) + end + for _,edge in ipairs(self.cluster_original_edges) do + self.graph:deleteEdge(edge) + end +end + + + +function Sugiyama:expandClusters() + + for _,node in ipairs(self.original_nodes) do + self.ranking:setRank(node, self.ranking:getRank(self.cluster_node[node])) + self.graph:addNode(node) + end + + for _,edge in ipairs(self.cluster_original_edges) do + for _,node in ipairs(edge.nodes) do + node:addEdge(edge) + end + self.graph:addEdge(edge) + end + + for _,edge in ipairs(self.cluster_edges) do + self.graph:deleteEdge(edge) + end +end + + +function Sugiyama:restoreCycles() + for _,edge in ipairs(self.graph.edges) do + edge.reversed = false + end +end + + + + + +-- done + +return Sugiyama diff --git a/graphics/pgf/base/tex/generic/graphdrawing/lua/pgf/gd/layered/crossing_minimization.lua b/graphics/pgf/base/tex/generic/graphdrawing/lua/pgf/gd/layered/crossing_minimization.lua new file mode 100644 index 0000000000..54a1fafb23 --- /dev/null +++ b/graphics/pgf/base/tex/generic/graphdrawing/lua/pgf/gd/layered/crossing_minimization.lua @@ -0,0 +1,81 @@ +-- Copyright 2012 by Till Tantau +-- +-- This file may be distributed and/or modified +-- +-- 1. under the LaTeX Project Public License and/or +-- 2. under the GNU Public License +-- +-- See the file doc/generic/pgf/licenses/LICENSE for more information + +-- @release $Header$ + + +local declare = require("pgf.gd.interface.InterfaceToAlgorithms").declare + + +--- +-- @section subsection {Crossing Minimization (Node Ordering)} +-- +-- The number of edge crossings in a layered drawing is determined by +-- the ordering of nodes at each of its layers. Therefore, crossing +-- minimization is the problem of reordering the nodes at each layer +-- so that the overall number of edge crossings is minimized. The +-- crossing minimization step takes a proper layering where every edge +-- connects nodes in neighbored layers, allowing algorithms to +-- minimize crossings layer by layer rather than all at once. While +-- this does not reduce the complexity of the problem, it does make it +-- considerably easier to understand and implement. Techniques based +-- on such an iterative approach are also known as layer-by-layer +-- sweep methods. They are used in many popular heuristics due to +-- their simplicity and the good results they produce. +-- +-- Sweeping refers to moving up and down from one layer to the next, +-- reducing crossings along the way. In layer-by-layer sweep methods, +-- an initial node ordering for one of the layers is computed +-- first. Depending on the sweep direction this can either be the +-- first layer or the last; in rare occasions the layer in the middle +-- is used instead. Followed by this, the actual layer-by-layer sweep +-- is performed. Given an initial ordering for the first layer $L_1$, a +-- downward sweep first holds the nodes in $L_1$ fixed while reordering +-- the nodes in the second layer $L_2$ to reduce the number of +-- crossings between $L_1$ and $L_2$. It then goes on to reorder the +-- third layer while holding the second layer fixed. This is continued +-- until all layers except for the first one have been +-- examined. Upward sweeping and sweeping from the middle work +-- analogous. +-- +-- Obviously, the central aspect of the layer-by-layer sweep is how +-- the nodes of a specific layer are reordered using a neighbored +-- layer as a fixed reference. This problem is known as one-sided +-- crossing minimization, which unfortunately is NP-hard. In the +-- following various heuristics to solve this problem are +-- presented. +-- +-- For more details, please see Section 4.1.4 of Pohlmann's Diploma +-- thesis. +-- +-- @end + + + +--- + +declare { + key = "sweep crossing minimization", + algorithm = require "pgf.gd.layered.CrossingMinimizationGansnerKNV1993", + phase = "crossing minimization", + phase_default = true, + + summary = [[" + Gansner et al. combine an initial ordering based on a depth-first + search with the median and greedy switch heuristics applied in the + form of an alternating layer-by-layer sweep based on a weighted + median. + "]], + documentation = [[" + For more details, please see Section~4.1.4 of Pohlmann's Diploma + thesis. + + This is the default algorithm for crossing minimization. + "]] +} diff --git a/graphics/pgf/base/tex/generic/graphdrawing/lua/pgf/gd/layered/cycle_removal.lua b/graphics/pgf/base/tex/generic/graphdrawing/lua/pgf/gd/layered/cycle_removal.lua new file mode 100644 index 0000000000..18670df14e --- /dev/null +++ b/graphics/pgf/base/tex/generic/graphdrawing/lua/pgf/gd/layered/cycle_removal.lua @@ -0,0 +1,136 @@ +-- Copyright 2012 by Till Tantau +-- +-- This file may be distributed and/or modified +-- +-- 1. under the LaTeX Project Public License and/or +-- 2. under the GNU Public License +-- +-- See the file doc/generic/pgf/licenses/LICENSE for more information + +-- @release $Header$ + + +local declare = require("pgf.gd.interface.InterfaceToAlgorithms").declare + + +--- +-- @section subsection {Cycle Removal} +-- +-- The Sugiyama method works only on directed \emph{acyclic} +-- graphs. For this reason, if the input graph is not (yet) acyclic, a +-- number of edges need to be redirected so that acyclicity arises. In +-- the following, the different options that allow you to fine-tune +-- this process are documented. +-- +-- @end + + + +--- + +declare { + key = "depth first cycle removal", + algorithm = require "pgf.gd.layered.CycleRemovalGansnerKNV1993", + phase = "cycle removal", + phase_default = true, + + summary = [[" + Selects a cycle removal algorithm that is especially + appropriate for graphs specified ``by hand''. + "]], + documentation = [[" + When graphs are created by humans manually, one can + make assumptions about the input graph that would otherwise not + be possible. For instance, it seems reasonable to assume that the + order in which nodes and edges are entered by the user somehow + reflects the natural flow the user has had in mind for the graph. + + In order to preserve the natural flow of the input graph, Gansner + et al.\ propose to remove cycles by performing a series of + depth-first searches starting at individual nodes in the order they + appear in the graph. This algorithm implicitly constructs a spanning + tree of the nodes reached during the searches. It thereby partitions + the edges of the graph into tree edges and non-tree edges. The + non-tree edges are further subdivided into forward edges, cross edges, + and back edges. Forward edges point from a tree nodes to one of their + descendants. Cross edges connect unrelated branches in the search tree. + Back edges connect descendants to one of their ancestors. It is not + hard to see that reversing back edges will not only introduce no new + cycles but will also make any directed graph acyclic. + Gansner et al.\ argue that this approach is more stable than others + in that fewer inappropriate edges are reversed compared to other + methods, despite the lack of a provable upper bound for the number + of reversed edges. + + See section~4.1.1 of Pohlmann's Diplom thesis for more details. + + This is the default algorithm for cycle removals. + "]] + } + +--- + +declare { + key = "prioritized greedy cycle removal", + algorithm = "pgf.gd.layered.CycleRemovalEadesLS1993", + phase = "cycle removal", + + summary = [[" + This algorithm implements a greedy heuristic of Eades et al.\ for + cycle removal that prioritizes sources and sinks. + "]], + documentation = [[" + See section~4.1.1 of Pohlmann's Diploma theses for details. + "]] +} + + +--- + +declare { + key = "greedy cycle removal", + algorithm = "pgf.gd.layered.CycleRemovalEadesLS1993", + phase = "cycle removal", + + summary = [[" + This algorithm implements a greedy heuristic of Eades et al.\ for + cycle removal that prioritizes sources and sinks. + "]], + documentation = [[" + See section~4.1.1 of Pohlmann's Diploma theses for details. + "]] + } + +--- + +declare { + key = "naive greedy cycle removal", + algorithm = "pgf.gd.layered.CycleRemovalBergerS1990a", + phase = "cycle removal", + + summary = [[" + This algorithm implements a greedy heuristic of Berger and Shor for + cycle removal. It is not really compared to the other heuristics and + only included for demonstration purposes. + "]], + documentation = [[" + See section~4.1.1 of Pohlmann's Diploma theses for details. + "]] + } + +--- + +declare { + key = "random greedy cycle removal", + algorithm = "pgf.gd.layered.CycleRemovalBergerS1990b", + phase = "cycle removal", + + summary = [[" + This algorithm implements a randomized greedy heuristic of Berger + and Shor for cycle removal. It, too, is not really compared to + the other heuristics and only included for demonstration purposes. + "]], + documentation = [[" + See section~4.1.1 of Pohlmann's Diploma theses for details. + "]] + }
\ No newline at end of file diff --git a/graphics/pgf/base/tex/generic/graphdrawing/lua/pgf/gd/layered/edge_routing.lua b/graphics/pgf/base/tex/generic/graphdrawing/lua/pgf/gd/layered/edge_routing.lua new file mode 100644 index 0000000000..eb9939c74c --- /dev/null +++ b/graphics/pgf/base/tex/generic/graphdrawing/lua/pgf/gd/layered/edge_routing.lua @@ -0,0 +1,49 @@ +-- Copyright 2012 by Till Tantau +-- +-- This file may be distributed and/or modified +-- +-- 1. under the LaTeX Project Public License and/or +-- 2. under the GNU Public License +-- +-- See the file doc/generic/pgf/licenses/LICENSE for more information + +-- @release $Header$ + + +local declare = require("pgf.gd.interface.InterfaceToAlgorithms").declare + + +--- +-- @section subsection {Edge Routing} +-- +-- The original layered drawing method described by Eades and Sugiyama +-- in does not include the routing or shaping of edges as a main +-- step. This makes sense if all nodes have the same size and +-- shape. In practical scenarios, however, this assumption often does +-- not hold. In these cases, advanced techniques may have to be +-- applied in order to avoid overlaps of nodes and edges. +-- +-- For more details, please see Section~4.1.5 of Pohlmann's Diploma +-- thesis. +-- +-- @end + + + +--- + +declare { + key = "polyline layer edge routing", + algorithm = require "pgf.gd.layered.EdgeRoutingGansnerKNV1993", + phase = "layer edge routing", + phase_default = true, + + summary = [[" + This edge routing algorithm uses polygonal lines to connect nodes. + "]], + documentation = [[" + For more details, please see Section~4.1.5 of Pohlmann's Diploma thesis. + + This is the default algorithm for edge routing. + "]] +} diff --git a/graphics/pgf/base/tex/generic/graphdrawing/lua/pgf/gd/layered/library.lua b/graphics/pgf/base/tex/generic/graphdrawing/lua/pgf/gd/layered/library.lua new file mode 100644 index 0000000000..950e02ffa3 --- /dev/null +++ b/graphics/pgf/base/tex/generic/graphdrawing/lua/pgf/gd/layered/library.lua @@ -0,0 +1,94 @@ +-- Copyright 2012 by Till Tantau +-- +-- This file may be distributed an/or modified +-- +-- 1. under the LaTeX Project Public License and/or +-- 2. under the GNU Public License +-- +-- See the file doc/generic/pgf/licenses/LICENSE for more information + +-- @release $Header$ + + + +--- +-- A ``layered'' layout of a graph tries to arrange the nodes in +-- consecutive horizontal layers (naturally, by rotating the graph, this +-- can be changed in to vertical layers) such that edges tend to be only +-- between nodes on adjacent layers. Trees, for instance, can always be +-- laid out in this way. This method of laying out a graph is especially +-- useful for hierarchical graphs. +-- +-- The method implemented in this library is often called the +-- \emph{Sugiyama method}, which is a rather advanced method of +-- assigning nodes to layers and positions on these layers. The same +-- method is also used in the popular GraphViz program, indeed, the +-- implementation in \tikzname\ is based on the same pseudo-code from the +-- same paper as the implementation used in GraphViz and both programs +-- will often generate the same layout (but not always, as explained +-- below). The current implementation is due to Jannis Pohlmann, who +-- implemented it as part of his Diploma thesis. Please consult this +-- thesis for a detailed explanation of the Sugiyama method and its +-- history: +-- % +-- \begin{itemize} +-- \item +-- Jannis Pohlmann, +-- \newblock \emph{Configurable Graph Drawing Algorithms +-- for the \tikzname\ Graphics Description Language,} +-- \newblock Diploma Thesis, +-- \newblock Institute of Theoretical Computer Science, Universit\"at +-- zu L\"ubeck, 2011.\\[.5em] +-- \newblock Available online via +-- \url{http://www.tcs.uni-luebeck.de/downloads/papers/2011/}\\ +-- \url{2011-configurable-graph-drawing-algorithms-jannis-pohlmann.pdf} +-- \\[.5em] +-- (Note that since the publication of this thesis some option names +-- have been changed. Most noticeably, the option name +-- |layered drawing| was changed to |layered layout|, which is somewhat +-- more consistent with other names used in the graph drawing +-- libraries. Furthermore, the keys for choosing individual +-- algorithms for the different algorithm phases, have all changed.) +-- \end{itemize} +-- +-- The Sugiyama methods lays out a graph in five steps: +-- % +-- \begin{enumerate} +-- \item Cycle removal. +-- \item Layer assignment (sometimes called node ranking). +-- \item Crossing minimization (also referred to as node ordering). +-- \item Node positioning (or coordinate assignment). +-- \item Edge routing. +-- \end{enumerate} +-- % +-- It turns out that behind each of these steps there lurks an +-- NP-complete problem, which means, in practice, that each step is +-- impossible to perform optimally for larger graphs. For this reason, +-- heuristics and approximation algorithms are used to find a ``good'' +-- way of performing the steps. +-- +-- A distinctive feature of Pohlmann's implementation of the Sugiyama +-- method for \tikzname\ is that the algorithms used for each of the +-- steps can easily be exchanged, just specify a different option. For +-- the user, this means that by specifying a different option and thereby +-- using a different heuristic for one of the steps, a better layout can +-- often be found. For the researcher, this means that one can very +-- easily test new approaches and new heuristics without having to +-- implement all of the other steps anew. +-- +-- @library + +local layered + + +-- Load declarations from: +require "pgf.gd.layered" + +-- Load algorithms from: +require "pgf.gd.layered.Sugiyama" +require "pgf.gd.layered.cycle_removal" +require "pgf.gd.layered.node_ranking" +require "pgf.gd.layered.crossing_minimization" +require "pgf.gd.layered.node_positioning" +require "pgf.gd.layered.edge_routing" + diff --git a/graphics/pgf/base/tex/generic/graphdrawing/lua/pgf/gd/layered/node_positioning.lua b/graphics/pgf/base/tex/generic/graphdrawing/lua/pgf/gd/layered/node_positioning.lua new file mode 100644 index 0000000000..e51382d642 --- /dev/null +++ b/graphics/pgf/base/tex/generic/graphdrawing/lua/pgf/gd/layered/node_positioning.lua @@ -0,0 +1,55 @@ +-- Copyright 2012 by Till Tantau +-- +-- This file may be distributed and/or modified +-- +-- 1. under the LaTeX Project Public License and/or +-- 2. under the GNU Public License +-- +-- See the file doc/generic/pgf/licenses/LICENSE for more information + +-- @release $Header$ + + +local declare = require("pgf.gd.interface.InterfaceToAlgorithms").declare + + +--- +-- @section subsection {Node Positioning (Coordinate Assignment)} +-- +-- The second last step of the Sugiyama method decides about the final +-- $x$- and $y$-coordinates of the nodes. The main objectives of this +-- step are to position nodes so that the number of edge bends is kept +-- small and edges are drawn as vertically as possible. Another goal +-- is to avoid node and edge overlaps which is crucial in particular +-- if the nodes are allowed to have non-uniform sizes. The +-- $y$-coordinates of the nodes have no influence on the number of +-- bends. Obviously, nodes need to be separated enough geometrically +-- so that they do not overlap. It feels natural to aim at separating +-- all layers in the drawing by the same amount. Large nodes, however, +-- may force node positioning algorithms to override this uniform +-- level distance in order to avoid overlaps. +-- +-- For more details, please see Section~4.1.2 of Pohlmann's Diploma thesis. +-- +-- @end + + + +--- + +declare { + key = "linear optimization node positioning", + algorithm = require "pgf.gd.layered.NodePositioningGansnerKNV1993", + phase = "node positioning", + phase_default = true, + + summary = [[" + This node positioning method, due to Gasner et al., is based on a + linear optimization problem. + "]], + documentation = [[" + For more details, please see Section~4.1.3 of Pohlmann's Diploma thesis. + + This is the default algorithm for layer assignments. + "]] +} diff --git a/graphics/pgf/base/tex/generic/graphdrawing/lua/pgf/gd/layered/node_ranking.lua b/graphics/pgf/base/tex/generic/graphdrawing/lua/pgf/gd/layered/node_ranking.lua new file mode 100644 index 0000000000..c663d9ce36 --- /dev/null +++ b/graphics/pgf/base/tex/generic/graphdrawing/lua/pgf/gd/layered/node_ranking.lua @@ -0,0 +1,72 @@ +-- Copyright 2012 by Till Tantau +-- +-- This file may be distributed and/or modified +-- +-- 1. under the LaTeX Project Public License and/or +-- 2. under the GNU Public License +-- +-- See the file doc/generic/pgf/licenses/LICENSE for more information + +-- @release $Header$ + + +local declare = require("pgf.gd.interface.InterfaceToAlgorithms").declare + + +--- +-- @section subsection {Layer Assignment (Node Ranking)} +-- +-- Algorithms for producing layered drawings place nodes on discrete +-- layers from top to bottom. Layer assignment is the problem of +-- finding a partition so that for all edges $e = (u,v) \in E(G)$ the +-- equation $\mathit{layer}(u) < \mathit{layer}(v)$ holds. Such a +-- partition is called a \emph{layering}. This definition can be extended by +-- introducing edge weights or priorities and minimum length +-- constraints which has practical applications and allows users to +-- fine-tune the results. +-- +-- For more details, please see Section~4.1.2 of Pohlmann's Diploma +-- thesis. +-- +-- @end + + + +--- + +declare { + key = "linear optimization layer assignment", + algorithm = require "pgf.gd.layered.NodeRankingGansnerKNV1993", + phase = "node ranking", + phase_default = true, + + summary = [[" + This layer assignment method, due to Gasner et al., is based on a + linear optimization problem. + "]], + documentation = [[" + For more details, please see Section~4.1.2 of Pohlmann's Diploma + thesis. + + This is the default algorithm for layer assignments. + "]] +} + + + +--- + +declare { + key = "minimum height layer assignment", + algorithm = "pgf.gd.layered.NodeRankingMinimumHeight", + phase = "node ranking", + + summary = [[" + This layer assignment method minimizes the height of the resulting graph. + "]], + documentation = [[" + For more details, please see Section~4.1.3 of Pohlmann's Diploma thesis. + "]] +} + + diff --git a/graphics/pgf/base/tex/generic/graphdrawing/lua/pgf/gd/lib.lua b/graphics/pgf/base/tex/generic/graphdrawing/lua/pgf/gd/lib.lua new file mode 100644 index 0000000000..cc39ddd607 --- /dev/null +++ b/graphics/pgf/base/tex/generic/graphdrawing/lua/pgf/gd/lib.lua @@ -0,0 +1,435 @@ +-- Copyright 2012 by Till Tantau +-- +-- This file may be distributed an/or modified +-- +-- 1. under the LaTeX Project Public License and/or +-- 2. under the GNU Public License +-- +-- See the file doc/generic/pgf/licenses/LICENSE for more information + +-- @release $Header$ + + + +--- +-- Basic library functions + +local lib = {} + +-- Declare namespace + +require("pgf.gd").lib = lib + + +-- General lib functions: + + +--- +-- Finds the first value in the |array| for which |test| is true. +-- +-- @param array An array to search in. +-- @param test A function that is applied to each element of the +-- array together with the index of the element and the +-- whole table. +-- +-- @return The value of the first value where the test is true. +-- @return The index of the first value where the test is true. +-- @return The function value of the first value where the test is +-- true (only returned if test is a function). +-- +function lib.find(array, test) + for i=1,#array do + local t = array[i] + local result = test(t,i,array) + if result then + return t,i,result + end + end +end + + +--- +-- Finds the first value in the |array| for which a function +-- returns a minimal value +-- +-- @param array An array to search in. +-- @param f A function that is applied to each element of the +-- array together with the index of the element and the +-- whole table. It should return an integer and, possibly, a value. +-- +-- Among all elements for which a non-nil integer is returned, let |i| +-- by the index of the element where this integer is minimal. +-- +-- @return |array[i]| +-- @return |i| +-- @return The return value(s) of the function at |array[i]|. +-- +function lib.find_min(array, f) + local best = math.huge + local best_result + local best_index + for i=1,#array do + local t = array[i] + local result, p = f(t,i,array) + if result and p < best then + best = p + best_result = result + best_index = i + end + end + if best_index then + return array[best_index],best_index,best_result,best + end +end + + + + +--- +-- Copies a table while preserving its metatable. +-- +-- @param source The table to copy. +-- @param target The table to which values are to be copied or |nil| if a new +-- table is to be allocated. +-- +-- @return The |target| table or a newly allocated table containing all +-- keys and values of the |source| table. +-- +function lib.copy(source, target) + if not target then + target = {} + end + for key, val in pairs(source) do + target[key] = val + end + return setmetatable(target, getmetatable(source)) +end + + +--- +-- Copies an array while preserving its metatable. +-- +-- @param source The array to copy. +-- @param target The array to which values are to be copied or |nil| if a new +-- table is to be allocated. The elements of the +-- |source| array will be added at the end. +-- +-- @return The |target| table or a newly allocated table containing all +-- keys and values of the |source| table. +-- +function lib.icopy(source, target) + target = target or {} + for _, val in ipairs(source) do + target[#target+1] = val + end + return setmetatable(target, getmetatable(source)) +end + + + + +--- +-- Apply a function to all pairs of a table, resulting in a new table. +-- +-- @param source The table. +-- @param fun A function taking two arguments (|val| and |key|, in +-- that order). Should return two values (a |new_val| and a +-- |new_key|). This pair will be inserted into the new table. If, +-- however, |new_key| is |nil|, the |new_value| will be inserted at +-- the position |key|. This means, in particular, that if the |fun| +-- takes only a single argument and returns only a single argument, +-- you have a ``classical'' value mapper. Also note that if +-- |new_value| is |nil|, the value is removed from the table. +-- +-- @return The new table. +-- +function lib.map(source, fun) + local target = {} + for key, val in pairs(source) do + local new_val, new_key = fun(val, key) + if new_key == nil then + new_key = key + end + target[new_key] = new_val + end + return target +end + + + +--- +-- Apply a function to all elements of an array, resulting in a new +-- array. +-- +-- @param source The array. +-- @param fun A function taking two arguments (|val| and |i|, the +-- current index). This function is applied to all elements of the +-- array. The result of this function is placed at the end of a new +-- array, expect when the function returns |nil|, in which case the +-- element is skipped. If this function is not provided (is |nil|), +-- the identity function is used. +-- @param new The target array (if |nil|, a new array is create). +-- % +--\begin{codeexample}[code only] +-- local a = lib.imap(array, function(v) if some_test(v) then return v end end) +--\end{codeexample} +-- +-- The above code is a filter that will remove all elements from the +-- array that do not pass |some_test|. +-- % +--\begin{codeexample}[code only] +-- lib.imap(a, lib.id, b) +--\end{codeexample} +-- +-- The above code has the same effect as |lib.icopy(a,b)|. +-- +-- @return The new array +-- +function lib.imap(source, fun, new) + if not new then + new = { } + end + for i, v in ipairs(source) do + new[#new+1] = fun(v, i) + end + return new +end + + +--- +-- Generate a random permutation of the numbers $1$ to $n$ in time +-- $O(n)$. Knuth's shuffle is used for this. +-- +-- @param n The desired size of the table +-- @return A random permutation + +function lib.random_permutation(n) + local p = {} + for i=1,n do + p[i] = i + end + for i=1,n-1 do + local j = lib.random(i,n) + p[i], p[j] = p[i], p[j] + end + return p +end + + +--- +-- The identity function, so you can write |lib.id| instead of +-- |function (x) return x end|. +-- + +function lib.id(...) + return ... +end + + + +--- +-- Tries to find an option in different objects that have an +-- options field. +-- +-- This function iterates over all objects given as parameters. In +-- each, it tries to find out whether the options field of the object +-- contains the option |name| and, if so, +-- returns the value. The important point is that checking whether the +-- option table of an object contains the name field is done using +-- |rawget| for all but the last parameter. This means that when you +-- write +-- % +--\begin{codeexample}[code only] +--lib.lookup_option("foo", vertex, graph) +--\end{codeexample} +-- % +-- and if |/graph drawing/foo| has an initial value set, if the +-- parameter is not explicitly set in a vertex, you will get the value +-- set for the graph or, if it is not set there either, the initial +-- value. In contrast, if you write +-- % +--\begin{codeexample}[code only] +-- vertex.options["foo"] or graph.options["foo"] +--\end{codeexample} +-- % +-- what happens is that the first access to |.options| will +-- \emph{always} return something when an initial parameter has been +-- set for the option |foo|. +-- +-- @param name The name of the options +-- @param ... Any number of objects. Each must have an options +-- field. +-- +-- @return The found option + +function lib.lookup_option(name, ...) + local list = {...} + for i=1,#list-1 do + local o = list[i].options + if o then + local v = rawget(o, name) + if v then + return v + end + end + end + return list[#list].options[name] +end + + + +--- +-- Turns a table |t| into a class in the sense of object oriented +-- programming. In detail, this means that |t| is augmented by +-- a |new| function, which takes an optional table of |initial| values +-- and which outputs a new table whose metatable is the +-- class. The |new| function will call the function |constructor| if +-- it exists. Furthermore, the class object's |__index| is set to itself +-- and its meta table is set to the |base_class| field of the +-- table. If |t| is |nil|, a new table is created. +-- +-- Here is a typical usage of this function: +-- % +--\begin{codeexample}[code only] +--local Point = lib.class {} +-- +--function Point:length() +-- return math.sqrt(self.x*self.x + self.y*self.y) +--end +-- +--local p = Point.new { x = 5, y = 6 } +-- +--print(p:length()) +--\end{codeexample} +-- % +-- We can subclass this as follows: +-- % +--\begin{codeexample}[code only] +--local Point3D = lib.class { base_class = Point } +-- +--function Point3D:length() +-- local l = Point.length(self) -- Call base class's function +-- return math.sqrt(l*l + self.z*self.zdy) +--end +-- +--local p = Point3D.new { x = 5, y = 6, z = 6 } +-- +--print(p:length()) +--\end{codeexample} +-- +-- @param t A table that gets augmented to a class. If |nil|, a new +-- table is created. +-- @return The augmented table. + +function lib.class(t) + t = t or {} + + -- First, setup indexing, if necessary + if not t.__index then + t.__index = t + end + + -- Second, setup new method, if necessary + t.new = t.new or + function (initial) + + -- Create new object + local obj = {} + for k,v in pairs(initial or {}) do + obj[k] = v + end + setmetatable(obj, t) + + if obj.constructor then + obj:constructor() + end + + return obj + end + + -- Third, setup inheritance, if necessary + if not getmetatable(t) then + setmetatable(t, t.base_class) + end + + return t +end + + + +--- +-- Returns a method that is loaded only on demand for a class. +-- +-- The idea behind this function is that you may have a class (or just +-- a table) for which some methods are needed only seldomly. In this +-- case, you can put these methods in a separate file and then use +-- |ondemand| to indicate that the methods are found in a +-- another file. +-- % +--\begin{codeexample}[code only] +-- -- File Foo.lua +-- local Foo = {} +-- function Foo.bar () ... end +-- function Foo.bar2 () ... end +-- Foo.bar3 = lib.ondemand("Foo_extra", Foo, "bar3") +-- Foo.bar4 = lib.ondemand("Foo_extra", Foo, "bar4") +-- +-- return Foo +-- +-- -- Foo_extra.lua +-- local Foo = require "Foo" +-- function Foo.bar3 () ... end +-- function Foo.bar4 () ... end +--\end{codeexample} +-- +-- @param filename The name of the file when extra methods are +-- located. +-- @param table The table for which the missing functions should be +-- loaded when they are accessed. +-- @param method The name of the method. +-- +-- @return A function that, when called, loads the filename using +-- |require| and, then, forwards the call to the method. + +function lib.ondemand(filename, table, name) + return function(...) + require (filename) + return table[name] (...) + end +end + + + +--- +-- This implements the a random number generator similar to the one +-- provided by Lua, but based on the tex.uniformdeviate primitive to +-- avoid differences in random numbers due to platform specifics. +-- +-- @param l Lower bound +-- @param u Upper bound +-- @return A random number +function lib.random(l,u) + local fraction_one = 268435456 + local r = tex.uniform_rand(fraction_one)/fraction_one + if l and u then + assert(l <= u) + return math.floor(r*(u-l+1)) + l + elseif l then + assert(1.0 <= l) + return math.floor(r*l) + 1.0 + else + return r + end +end + +--- +-- Provide the seed for the random number generator +-- +-- @param seed random seed +function lib.randomseed(seed) + tex.init_rand(seed) +end + +-- Done + +return lib diff --git a/graphics/pgf/base/tex/generic/graphdrawing/lua/pgf/gd/lib/Bezier.lua b/graphics/pgf/base/tex/generic/graphdrawing/lua/pgf/gd/lib/Bezier.lua new file mode 100644 index 0000000000..e4fe21d942 --- /dev/null +++ b/graphics/pgf/base/tex/generic/graphdrawing/lua/pgf/gd/lib/Bezier.lua @@ -0,0 +1,160 @@ +-- Copyright 2013 by Till Tantau +-- +-- This file may be distributed an/or modified +-- +-- 1. under the LaTeX Project Public License and/or +-- 2. under the GNU Public License +-- +-- See the file doc/generic/pgf/licenses/LICENSE for more information + +-- @release $Header$ + + + +--- +-- This library offers a number of methods for working with Bezi\'er +-- curves. + +local Bezier = {} + +-- Namespace +require("pgf.gd.lib").Bezier = Bezier + + +-- Imports + +local Coordinate = require 'pgf.gd.model.Coordinate' + + +--- +-- Compute a point ``along a curve at a time''. You provide the four +-- coordinates of the curve and a time. You get a point on the curve +-- as return value as well as the two support vector for curve +-- before this point and two support vectors for the curve after the +-- point. +-- +-- For speed reasons and in order to avoid superfluous creation of +-- lots of tables, all values are provided and returned as pairs of +-- values rather than as |Coordinate| objects. +-- +-- @param ax The coordinate where the curve starts. +-- @param ay +-- @param bx The first support point. +-- @param by +-- @param cx The second support point. +-- @param cy +-- @param dx The coordinate where the curve ends. +-- @param dy +-- @param t A time (a number). +-- +-- @return The point |p| on the curve at time |t| ($x$-part). +-- @return The point |p| on the curve at time |t| ($y$-part). +-- @return The first support point of the curve between |a| and |p| ($x$-part). +-- @return The first support point of the curve between |a| and |p| ($y$-part). +-- @return The second support point of the curve between |a| and |p| ($x$-part). +-- @return The second support point of the curve between |a| and |p| ($y$-part). +-- @return The first support point of the curve between |p| and |d| ($x$-part). +-- @return The first support point of the curve between |p| and |d| ($y$-part). +-- @return The second support point of the curve between |p| and |d| ($x$-part). +-- @return The second support point of the curve between |p| and |d| ($y$-part). + +function Bezier.atTime(ax,ay,bx,by,cx,cy,dx,dy,t) + + local s = 1-t + + local ex, ey = ax*s + bx*t, ay*s + by*t + local fx, fy = bx*s + cx*t, by*s + cy*t + local gx, gy = cx*s + dx*t, cy*s + dy*t + + local hx, hy = ex*s + fx*t, ey*s + fy*t + local ix, iy = fx*s + gx*t, fy*s + gy*t + + local jx, jy = hx*s + ix*t, hy*s + iy*t + + return jx, jy, ex, ey, hx, hy, ix, iy, gx, gy +end + + +--- +-- The ``coordinate version'' of the |atTime| function, where both the +-- parameters and the return values are coordinate objects. + +function Bezier.atTimeCoordinates(a,b,c,d,t) + local jx, jy, ex, ey, hx, hy, ix, iy, gx, gy = + Bezier.atTime(a.x,a.y,b.x,b.y,c.x,c.y,d.x,d.y,t) + + return + Coordinate.new(jx, jy), + Coordinate.new(ex, ey), + Coordinate.new(hx, hy), + Coordinate.new(ix, iy), + Coordinate.new(gx, gy) +end + + +--- +-- Computes the support points of a Bezier curve based on two points +-- on the curves at certain times. +-- +-- @param from The start point of the curve +-- @param p1 A first point on the curve +-- @param t1 A time when this point should be reached +-- @param p2 A second point of the curve +-- @param t2 A time when this second point should be reached +-- @param to The end of the curve +-- +-- @return sup1 A first support point of the curve +-- @return sup2 A second support point of the curve + +function Bezier.supportsForPointsAtTime(from, p1, t1, p2, t2, to) + + local s1 = 1 - t1 + local s2 = 1 - t2 + + local f1a = s1^3 + local f1b = t1 * s1^2 * 3 + local f1c = t1^2 * s1 * 3 + local f1d = t1^3 + + local f2a = s2^3 + local f2b = t2 * s2^2 * 3 + local f2c = t2^2 * s2 * 3 + local f2d = t2^3 + + -- The system: + -- p1.x - from.x * f1a - to.x * f1d = sup1.x * f1b + sup2.x * f1c + -- p2.x - from.x * f2a - to.x * f2d = sup1.x * f2b + sup2.x * f2c + -- + -- p1.y - from.y * f1a - to.y * f1d = sup1.y * f1b + sup2.y * f1c + -- p2.y - from.y * f2a - to.y * f2d = sup1.y * f2b + sup2.y * f2c + + local a = f1b + local b = f1c + local c = p1.x - from.x * f1a - to.x * f1d + local d = f2b + local e = f2c + local f = p2.x - from.x * f2a - to.x * f2d + + local det = a*e - b*d + local x1 = -(b*f - e*c)/det + local x2 = -(c*d - a*f)/det + + local c = p1.y - from.y * f1a - to.y * f1d + local f = p2.y - from.y * f2a - to.y * f2d + + local det = a*e - b*d + local y1 = -(b*f - e*c)/det + local y2 = -(c*d - a*f)/det + + return Coordinate.new(x1,y1), Coordinate.new(x2,y2) + +end + + + + + + +-- Done + +return Bezier
\ No newline at end of file diff --git a/graphics/pgf/base/tex/generic/graphdrawing/lua/pgf/gd/lib/DepthFirstSearch.lua b/graphics/pgf/base/tex/generic/graphdrawing/lua/pgf/gd/lib/DepthFirstSearch.lua new file mode 100644 index 0000000000..564b8f6839 --- /dev/null +++ b/graphics/pgf/base/tex/generic/graphdrawing/lua/pgf/gd/lib/DepthFirstSearch.lua @@ -0,0 +1,125 @@ +-- Copyright 2011 by Jannis Pohlmann +-- Copyright 2012 by Till Tantau +-- +-- This file may be distributed an/or modified +-- +-- 1. under the LaTeX Project Public License and/or +-- 2. under the GNU Public License +-- +-- See the file doc/generic/pgf/licenses/LICENSE for more information + +-- @release $Header$ + + + +--- The DepthFirstSearch class implements a generic depth first function. It does not +-- require that it is run on graphs, but can be used for anything where a visit function and +-- a complete function is available. + +local DepthFirstSearch = {} +DepthFirstSearch.__index = DepthFirstSearch + +-- Namespace +require("pgf.gd.lib").DepthFirstSearch = DepthFirstSearch + +-- Imports +local Stack = require "pgf.gd.lib.Stack" + + + +-- TT: TODO Jannis: Please document... + +function DepthFirstSearch.new(init_func, visit_func, complete_func) + local dfs = { + init_func = init_func, + visit_func = visit_func, + complete_func = complete_func, + + stack = Stack.new(), + discovered = {}, + visited = {}, + completed = {}, + } + setmetatable(dfs, DepthFirstSearch) + return dfs +end + + + +function DepthFirstSearch:run() + self:reset() + self.init_func(self) + + while self.stack:getSize() > 0 do + local data = self.stack:peek() + + if not self:getVisited(data) then + if self.visit_func then + self.visit_func(self, data) + end + else + if self.complete_func then + self.complete_func(self, data) + end + self:setCompleted(data, true) + self.stack:pop() + end + end +end + + + +function DepthFirstSearch:reset() + self.discovered = {} + self.visited = {} + self.completed = {} + self.stack = Stack.new() +end + + + +function DepthFirstSearch:setDiscovered(data, discovered) + self.discovered[data] = discovered +end + + + +function DepthFirstSearch:getDiscovered(data) + return self.discovered[data] +end + + + +function DepthFirstSearch:setVisited(data, visited) + self.visited[data] = visited +end + + + +function DepthFirstSearch:getVisited(data) + return self.visited[data] +end + + + +function DepthFirstSearch:setCompleted(data, completed) + self.completed[data] = completed +end + + + +function DepthFirstSearch:getCompleted(data) + return self.completed[data] +end + + + +function DepthFirstSearch:push(data) + self.stack:push(data) +end + + + +-- Done + +return DepthFirstSearch
\ No newline at end of file diff --git a/graphics/pgf/base/tex/generic/graphdrawing/lua/pgf/gd/lib/Direct.lua b/graphics/pgf/base/tex/generic/graphdrawing/lua/pgf/gd/lib/Direct.lua new file mode 100644 index 0000000000..81728a1a44 --- /dev/null +++ b/graphics/pgf/base/tex/generic/graphdrawing/lua/pgf/gd/lib/Direct.lua @@ -0,0 +1,95 @@ +-- Copyright 2012 by Till Tantau +-- +-- This file may be distributed an/or modified +-- +-- 1. under the LaTeX Project Public License and/or +-- 2. under the GNU Public License +-- +-- See the file doc/generic/pgf/licenses/LICENSE for more information + +-- @release $Header$ + + + +--- Direct is a class that collects algorithms for computing new +-- versions of a graph where arcs point in certain directions. + +local Direct = {} + +-- Namespace +require("pgf.gd.lib").Direct = Direct + +-- Imports +local Digraph = require "pgf.gd.model.Digraph" + + +--- Compute a digraph from a syntactic digraph. +-- +-- This function takes a syntactic digraph and compute a new digraph +-- where all arrow point in the "semantic direction" of the syntactic +-- arrows. For instance, while "a <- b" will cause an arc from a to be +-- to be added to the syntactic digraph, calling this function will +-- return a digraph in which there is an arc from b to a rather than +-- the other way round. In detail, "a <- b" is translated as just +-- described, "a -> b" yields an arc from a to b as expected, "a <-> b" +-- and "a -- b" yield arcs in both directions and, finally, "a -!- b" +-- yields no arc at all. +-- +-- @param syntactic_digraph A syntactic digraph, usually the "input" +-- graph as specified syntactically be the user. +-- +-- @return A new "semantic" digraph object. + +function Direct.digraphFromSyntacticDigraph(syntactic_digraph) + local digraph = Digraph.new(syntactic_digraph) -- copy + + -- Now go over all arcs of the syntactic_digraph and turn them into + -- arcs with the correct direction in the digraph: + for _,a in ipairs(syntactic_digraph.arcs) do + for _,m in ipairs(a.syntactic_edges) do + local direction = m.direction + if direction == "->" then + digraph:connect(a.tail, a.head) + elseif direction == "<-" then + digraph:connect(a.head, a.tail) + elseif direction == "--" or direction == "<->" then + digraph:connect(a.tail, a.head) + digraph:connect(a.head, a.tail) + end + -- Case -!-: No edges... + end + end + + return digraph +end + + +--- Turn an arbitrary graph into a directed graph +-- +-- Takes a digraph as input and returns its underlying undirected +-- graph, coded as a digraph. This means that between any two vertices +-- if there is an arc in one direction, there is also one in the other. +-- +-- @param digraph A directed graph +-- +-- @return The underlying undirected graph of digraph. + +function Direct.ugraphFromDigraph(digraph) + local ugraph = Digraph.new(digraph) + + -- Now go over all arcs of the syntactic_digraph and turn them into + -- arcs with the correct direction in the digraph: + for _,a in ipairs(digraph.arcs) do + ugraph:connect(a.head,a.tail) + ugraph:connect(a.tail,a.head) + end + + return ugraph +end + + + + +-- Done + +return Direct diff --git a/graphics/pgf/base/tex/generic/graphdrawing/lua/pgf/gd/lib/Event.lua b/graphics/pgf/base/tex/generic/graphdrawing/lua/pgf/gd/lib/Event.lua new file mode 100644 index 0000000000..880796ce97 --- /dev/null +++ b/graphics/pgf/base/tex/generic/graphdrawing/lua/pgf/gd/lib/Event.lua @@ -0,0 +1,98 @@ +-- Copyright 2012 by Till Tantau +-- +-- This file may be distributed an/or modified +-- +-- 1. under the LaTeX Project Public License and/or +-- 2. under the GNU Public License +-- +-- See the file doc/generic/pgf/licenses/LICENSE for more information + +-- @release $Header$ + + + +--- +-- Events are used to communicate ``interesting'' events from the +-- parser to the graph drawing algorithms. +-- +-- As a syntactic description of some graph is being parsed, vertices, +-- arcs, and a digraph object representing this graph get +-- constructed. However, even though syntactic annotations such as +-- options for the vertices and arcs are attached to them and can be +-- accessed through the graph objects, some syntactic information is +-- neither represented in the digraph object nor in the vertices and +-- the arcs. A typical example is a ``missing'' node in a tree: Since +-- it is missing, there is neither a vertex object nor arc objects +-- representing it. It is also not a global option of the graph. +-- +-- For these reasons, in addition to the digraph object itself, +-- additional information can be passed by a parser to graph drawing +-- algorithms through the means of events. Each |Event| consists of a +-- |kind| field, which is just some string, and a |parameters| field, +-- which stores additional, kind-specific information. As a graph is +-- being parsed, a string of events is accumulated and is later on +-- available through the |events| field of the graph drawing scope. +-- +-- The following events are created during the parsing process by the +-- standard parsers of \tikzname: +-- % +-- \begin{itemize} +-- \item[|node|] When a node of the input graph has been parsed and +-- a |Vertex| object has been created for it, an event with kind +-- |node| is created. The |parameter| of this event is the +-- just-created vertex. +-- +-- The same kind of event is used to indicate ``missing'' nodes. In +-- this case, the |parameters| field is |nil|. +-- \item[|edge|] When an edge of the input graph has been parsed, an +-- event is created of kind |edge|. The |parameters| field will store +-- an array with two entries: The first is the |Arc| object whose +-- |syntactic_edges| field stores the |edge|. The second is the index +-- of the edge inside the |syntactic_edges| field. +-- \item[|begin|] +-- Signals the beginning of a group, which will be ended with a +-- corresponding |end| event later on. The |parameters| field will +-- indicate the kind of group. Currently, only the string +-- |"descendants"| is used as |parameters|, indicating the start of +-- several nodes that are descendants of a given node. This +-- information can be used by algorithms for reconstructing the +-- input structure of trees. +-- \item[|end|] Signals the end of a group begun by a |begin| event +-- earlier on. +-- \end{itemize} +-- +-- @field kind A string representing the kind of the events. +-- @field parameters Kind-specific parameters. +-- @field index A number that stores the events logical position in +-- the sequence of events. The number need not be an integer array +-- index. +-- +local Event = {} +Event.__index = Event + + +-- Namespace +require("pgf.gd.lib").Event = Event + + + +--- +-- Create a new event object +-- +-- @param initial Initial fields of the new event. +-- +-- @return The new object + +function Event.new(values) + local new = {} + for k,v in pairs(values) do + new[k] = v + end + return setmetatable(new, Event) +end + + + +-- done + +return Event
\ No newline at end of file diff --git a/graphics/pgf/base/tex/generic/graphdrawing/lua/pgf/gd/lib/LookupTable.lua b/graphics/pgf/base/tex/generic/graphdrawing/lua/pgf/gd/lib/LookupTable.lua new file mode 100644 index 0000000000..fc2043d523 --- /dev/null +++ b/graphics/pgf/base/tex/generic/graphdrawing/lua/pgf/gd/lib/LookupTable.lua @@ -0,0 +1,111 @@ +-- Copyright 2012 by Till Tantau +-- +-- This file may be distributed an/or modified +-- +-- 1. under the LaTeX Project Public License and/or +-- 2. under the GNU Public License +-- +-- See the file doc/generic/pgf/licenses/LICENSE for more information + +-- @release $Header$ + + + +--- +-- This table provides two utility functions for managing ``lookup +-- tables''. Such a table is a mixture of an array and a hashtable: +-- It stores (only) tables. Each table is stored once in a normal +-- array position. Additionally, the lookup table is also indexed at +-- the position of the table (used as a key) and this position is set +-- to |true|. This means that you can test whether a table |t| is in the +-- lookup table |l| simply by testing whether |l[t]| is true. +-- +local LookupTable = {} + +-- Namespace +require("pgf.gd.lib").LookupTable = LookupTable + + + +--- +-- Add all elements in the |array| to a lookup table. If an element of +-- the array is already present in the table, it will not be added +-- again. +-- +-- This operation takes time $O(|\verb!array!|)$. +-- +-- @param l Lookup table +-- @param array An array of to-be-added tables. + +function LookupTable.add(l, array) + for i=1,#array do + local t = array[i] + if not l[t] then + l[t] = true + l[#l + 1] = t + end + end +end + + +--- +-- Add one element to a lookup table. If it is already present in the +-- table, it will not be added again. +-- +-- This operation takes time $O(1)$. +-- +-- @param l Lookup table +-- @param e The to-be-added element. + +function LookupTable.addOne(l, e) + if not l[e] then + l[e] = true + l[#l + 1] = e + end +end + + +--- +-- Remove tables from a lookup table. +-- +-- Note that this operation is pretty expensive insofar as it will +-- always cost a traversal of the whole lookup table. However, this is +-- also the maximum cost, even when a lot of entries need to be +-- deleted. Thus, it is much better to ``pool'' multiple remove +-- operations in a single one. +-- +-- This operation takes time $O(\max\{|\verb!array!|, |\verb!l!|\})$. +-- +-- @param l Lookup table +-- @param t An array of to-be-removed tables. + +function LookupTable.remove(l, array) + -- Step 1: Mark all to-be-deleted entries + for i=1,#array do + local t = array[i] + if l[t] then + l[t] = false + end + end + + -- Step 2: Collect garbage... + local target = 1 + for i=1,#l do + local t = l[i] + if l[t] == false then + l[t] = nil + else + l[target] = t + target = target + 1 + end + end + for i=#l,target,-1 do + l[i] = nil + end +end + + + +-- Done + +return LookupTable
\ No newline at end of file diff --git a/graphics/pgf/base/tex/generic/graphdrawing/lua/pgf/gd/lib/PathLengths.lua b/graphics/pgf/base/tex/generic/graphdrawing/lua/pgf/gd/lib/PathLengths.lua new file mode 100644 index 0000000000..4bfa896ef9 --- /dev/null +++ b/graphics/pgf/base/tex/generic/graphdrawing/lua/pgf/gd/lib/PathLengths.lua @@ -0,0 +1,209 @@ +-- Copyright 2011 by Jannis Pohlmann +-- Copyright 2012 by Till Tantau +-- +-- This file may be distributed an/or modified +-- +-- 1. under the LaTeX Project Public License and/or +-- 2. under the GNU Public License +-- +-- See the file doc/generic/pgf/licenses/LICENSE for more information + +-- @release $Header$ + + + +--- +-- This table provides algorithms for computing distances between +-- nodes of a graph (in the sense of path lengths). + +local PathLengths = {} + +-- Namespace +require("pgf.gd.lib").PathLengths = PathLengths + +-- Import +local PriorityQueue = require "pgf.gd.lib.PriorityQueue" + + + +--- +-- Performs the Dijkstra algorithm to solve the single-source shortest path problem. +-- +-- The algorithm computes the shortest paths from |source| to all nodes +-- in the graph. It also generates a table with distance level sets, each of +-- which contain all nodes that have the same corresponding distance to +-- |source|. Finally, a mapping of nodes to their parents along the +-- shortest paths is generated to allow the reconstruction of the paths +-- that were chosen by the Dijkstra algorithm. +-- +-- @param graph The graph to compute the shortest paths for. +-- @param source The node to compute the distances to. +-- +-- @return A mapping of nodes to their distance to |source|. +-- @return An array of distance level sets. The set at index |i| contains +-- all nodes that have a distance of |i| to |source|. +-- @return A mapping of nodes to their parents to allow the reconstruction +-- of the shortest paths chosen by the Dijkstra algorithm. +-- +function PathLengths.dijkstra(graph, source) + local distance = {} + local levels = {} + local parent = {} + + local queue = PriorityQueue.new() + + -- reset the distance of all nodes and insert them into the priority queue + for _,node in ipairs(graph.nodes) do + if node == source then + distance[node] = 0 + parent[node] = nil + queue:enqueue(node, distance[node]) + else + distance[node] = #graph.nodes + 1 -- this is about infinity ;) + queue:enqueue(node, distance[node]) + end + end + + while not queue:isEmpty() do + local u = queue:dequeue() + + assert(distance[u] < #graph.nodes + 1, 'the graph is not connected, Dijkstra will not work') + + if distance[u] > 0 then + levels[distance[u]] = levels[distance[u]] or {} + table.insert(levels[distance[u]], u) + end + + for _,edge in ipairs(u.edges) do + local v = edge:getNeighbour(u) + local alternative = distance[u] + 1 + if alternative < distance[v] then + distance[v] = alternative + + parent[v] = u + + -- update the priority of v + queue:updatePriority(v, distance[v]) + end + end + end + + return distance, levels, parent +end + + + + +--- +-- Performs the Floyd-Warshall algorithm to solve the all-source shortest path problem. +-- +-- @param graph The graph to compute the shortest paths for. +-- +-- @return A distance matrix +-- +function PathLengths.floydWarshall(graph) + local distance = {} + local infinity = math.huge + + for _,i in ipairs(graph.nodes) do + distance[i] = {} + for _,j in ipairs(graph.nodes) do + distance[i][j] = infinity + end + end + + for _,i in ipairs(graph.nodes) do + for _,edge in ipairs(i.edges) do + local j = edge:getNeighbour(i) + distance[i][j] = edge.weight or 1 + end + end + + for _,k in ipairs(graph.nodes) do + for _,i in ipairs(graph.nodes) do + for _,j in ipairs(graph.nodes) do + distance[i][j] = math.min(distance[i][j], distance[i][k] + distance[k][j]) + end + end + end + + return distance +end + + + + +--- +-- Computes the pseudo diameter of a graph. +-- +-- The diameter of a graph is the maximum of the shortest paths between +-- any pair of nodes in the graph. A pseudo diameter is an approximation +-- of the diameter that is computed by picking a starting node |u| and +-- finding a node |v| that is farthest away from |u| and has the smallest +-- degree of all nodes that have the same distance to |u|. The algorithm +-- continues with |v| as the new starting node and iteratively tries +-- to find an end node that is generates a larger pseudo diameter. +-- It terminates as soon as no such end node can be found. +-- +-- @param graph The graph. +-- +-- @return The pseudo diameter of the graph. +-- @return The start node of the corresponding approximation of a maximum +-- shortest path. +-- @return The end node of that path. +-- +function PathLengths.pseudoDiameter(graph) + + -- find a node with minimum degree + local start_node = graph.nodes[1] + for _,node in ipairs(graph.nodes) do + if node:getDegree() < start_node:getDegree() then + start_node = node + end + end + + assert(start_node) + + local old_diameter = 0 + local diameter = 0 + local end_node = nil + + while true do + local distance, levels = PathLengths.dijkstra(graph, start_node) + + -- the number of levels is the same as the distance of the nodes + -- in the last level to the start node + old_diameter = diameter + diameter = #levels + + -- abort if the diameter could not be improved + if diameter == old_diameter then + end_node = levels[#levels][1] + break + end + + -- select the node with the smallest degree from the last level as + -- the start node for the next iteration + start_node = levels[#levels][1] + for _,node in ipairs(levels[#levels]) do + if node:getDegree() < start_node:getDegree() then + start_node = node + end + end + + assert(start_node) + end + + assert(start_node) + assert(end_node) + + return diameter, start_node, end_node +end + + + + + +-- Done + +return PathLengths
\ No newline at end of file diff --git a/graphics/pgf/base/tex/generic/graphdrawing/lua/pgf/gd/lib/PriorityQueue.lua b/graphics/pgf/base/tex/generic/graphdrawing/lua/pgf/gd/lib/PriorityQueue.lua new file mode 100644 index 0000000000..3fd29cdb74 --- /dev/null +++ b/graphics/pgf/base/tex/generic/graphdrawing/lua/pgf/gd/lib/PriorityQueue.lua @@ -0,0 +1,342 @@ +-- Copyright 2011 by Jannis Pohlmann +-- Copyright 2012 by Till Tantau +-- +-- This file may be distributed an/or modified +-- +-- 1. under the LaTeX Project Public License and/or +-- 2. under the GNU Public License +-- +-- See the file doc/generic/pgf/licenses/LICENSE for more information + +-- @release $Header$ + + + +--- +-- A PriorityQueue supports operations for quickly finding the minimum from a set of elements +-- +-- Its implementation is based on (simplified) Fibonacci heaps. +local PriorityQueue = {} +PriorityQueue.__index = PriorityQueue + + +-- Namespace +local lib = require "pgf.gd.lib" +lib.PriorityQueue = PriorityQueue + + + +-- Local declarations +local FibonacciHeap = {} +local FibonacciHeapNode = {} + + + + +--- Creates a new priority queue +-- +-- @return The newly created queue + +function PriorityQueue.new() + local queue = { + heap = FibonacciHeap.new(), + nodes = {}, + values = {}, + } + setmetatable(queue, PriorityQueue) + return queue +end + + + +--- Add an element with a certain priority to the queue +-- +-- @param value An object +-- @param priority Its priority + +function PriorityQueue:enqueue(value, priority) + local node = self.heap:insert(priority) + self.nodes[value] = node + self.values[node] = value +end + + + +--- Removes the element with the minimum priority from the queue +-- +-- @return The element with the minimum priority + +function PriorityQueue:dequeue() + local node = self.heap:extractMinimum() + + if node then + local value = self.values[node] + self.nodes[value] = nil + self.values[node] = nil + return value + else + return nil + end +end + + + +--- Lower the priority of an element of a queue +-- +-- @param value An object +-- @param priority A new priority, which must be lower than the old priority + +function PriorityQueue:updatePriority(value, priority) + local node = self.nodes[value] + assert(node, 'updating the priority of ' .. tostring(value) .. ' failed because it is not in the priority queue') + self.heap:updateValue(node, priority) +end + + + +--- Tests, whether the queue is empty +-- +-- @return True, if the queue is empty + +function PriorityQueue:isEmpty() + return #self.heap.trees == 0 +end + + + + + + +-- Internals: An implementation of Fibonacci heaps. +FibonacciHeap.__index = FibonacciHeap + + +function FibonacciHeap.new() + local heap = { + trees = trees or {}, + minimum = nil, + } + setmetatable(heap, FibonacciHeap) + return heap +end + + + +function FibonacciHeap:insert(value) + local node = FibonacciHeapNode.new(value) + local heap = FibonacciHeap.new() + table.insert(heap.trees, node) + self:merge(heap) + return node +end + + + +function FibonacciHeap:merge(other) + for _, tree in ipairs(other.trees) do + table.insert(self.trees, tree) + end + self:updateMinimum() +end + + + +function FibonacciHeap:extractMinimum() + if self.minimum then + local minimum = self:removeTableElement(self.trees, self.minimum) + + for _, child in ipairs(minimum.children) do + child.root = child + table.insert(self.trees, child) + end + + local same_degrees_found = true + while same_degrees_found do + same_degrees_found = false + + local degrees = {} + + for _, root in ipairs(self.trees) do + local degree = root:getDegree() + + if degrees[degree] then + if root.value < degrees[degree].value then + self:linkRoots(root, degrees[degree]) + else + self:linkRoots(degrees[degree], root) + end + + degrees[degree] = nil + same_degrees_found = true + break + else + degrees[degree] = root + end + end + end + + self:updateMinimum() + + return minimum + end +end + + + +function FibonacciHeap:updateValue(node, value) + local old_value = node.value + local new_value = value + + if new_value <= old_value then + self:decreaseValue(node, value) + else + assert(false, 'FibonacciHeap:increaseValue is not implemented yet') + end +end + + + +function FibonacciHeap:decreaseValue(node, value) + assert(value <= node.value) + + node.value = value + + if node.value < node.parent.value then + local parent = node.parent + self:cutFromParent(node) + + if not parent:isRoot() then + if parent.marked then + self:cutFromParent(parent) + else + parent.marked = true + end + end + end + + if node.value < self.minimum.value then + self.minimum = node + end +end + + + +function FibonacciHeap:delete(node) + self:decreaseValue(node, -math.huge) + self:extractMinimum() +end + + + +function FibonacciHeap:linkRoots(root, child) + child.root = root + child.parent = root + + child = self:removeTableElement(self.trees, child) + table.insert(root.children, child) + + return root +end + + + +function FibonacciHeap:cutFromParent(node) + local parent = node.parent + + node.root = node + node.parent = node + node.marked = false + + node = self:removeTableElement(parent.children, node) + table.insert(self.trees, node) +end + + + +function FibonacciHeap:updateMinimum() + self.minimum = self.trees[1] + + for _, root in ipairs(self.trees) do + if root.value < self.minimum.value then + self.minimum = root + end + end +end + + + +function FibonacciHeap:removeTableElement(input_table, element) + for i = 1, #input_table do + if input_table[i] == element then + return table.remove(input_table, i) + end + end +end + + + + +-- Now come the nodes + +FibonacciHeapNode.__index = FibonacciHeapNode + +function FibonacciHeapNode.new(value, root, parent) + local node = { + value = value, + children = {}, + marked = false, + root = nil, + parent = nil, + } + setmetatable(node, FibonacciHeapNode) + + if root then + node.root = root + node.parent = parent + else + node.root = node + node.parent = node + end + + return node +end + +function FibonacciHeapNode:addChild(value) + local child = FibonacciHeapNode.new(value, self.root, self) + table.insert(self.children, child) +end + +function FibonacciHeapNode:getDegree() + return #self.children +end + + + +function FibonacciHeapNode:setRoot(root) + self.root = root + + if root == self then + self.parent = root + end + + if #self.children > 0 then + for _, child in ipairs(self.children) do + child.root = root + end + end +end + + + +function FibonacciHeapNode:isRoot() + return self.root == self +end + + + + + + +-- done + +return PriorityQueue
\ No newline at end of file diff --git a/graphics/pgf/base/tex/generic/graphdrawing/lua/pgf/gd/lib/Simplifiers.lua b/graphics/pgf/base/tex/generic/graphdrawing/lua/pgf/gd/lib/Simplifiers.lua new file mode 100644 index 0000000000..bcb7276190 --- /dev/null +++ b/graphics/pgf/base/tex/generic/graphdrawing/lua/pgf/gd/lib/Simplifiers.lua @@ -0,0 +1,279 @@ +-- Copyright 2012 by Till Tantau +-- +-- This file may be distributed an/or modified +-- +-- 1. under the LaTeX Project Public License and/or +-- 2. under the GNU Public License +-- +-- See the file doc/generic/pgf/licenses/LICENSE for more information + +-- @release $Header$ + + + +--- The Simplifiers class is a singleton object. +-- Its methods allow implement methods for simplifying graphs, for instance +-- for removing loops or multiedges or computing spanning trees. + +local Simplifiers = {} + +-- Namespace +local lib = require "pgf.gd.lib" +lib.Simplifiers = Simplifiers + + + + +-- Imports + +local Edge = require "pgf.gd.deprecated.Edge" +local Node = require "pgf.gd.deprecated.Node" + + + + + +--- Algorithm to classify edges of a DFS search tree. +-- +-- TODO Jannis: document this algorithm as soon as it is completed and bug-free. +-- TT: Replace this algorithm by something else, perhaps? +-- +function Simplifiers:classifyEdges(graph) + local discovered = {} + local visited = {} + local recursed = {} + local completed = {} + + local tree_and_forward_edges = {} + local cross_edges = {} + local back_edges = {} + + local stack = {} + + local function push(node) + table.insert(stack, node) + end + + local function peek() + return stack[#stack] + end + + local function pop() + return table.remove(stack) + end + + local initial_nodes = graph.nodes + + for i=#initial_nodes,1,-1 do + local node = initial_nodes[i] + push(node) + discovered[node] = true + end + + while #stack > 0 do + local node = peek() + local edges_to_traverse = {} + + visited[node] = true + + if not recursed[node] then + recursed[node] = true + + local out_edges = node:getOutgoingEdges() + for _,edge in ipairs(out_edges) do + local neighbour = edge:getNeighbour(node) + + if not discovered[neighbour] then + table.insert(tree_and_forward_edges, edge) + table.insert(edges_to_traverse, edge) + else + if not completed[neighbour] then + if not visited[neighbour] then + table.insert(tree_and_forward_edges, edge) + table.insert(edges_to_traverse, edge) + else + table.insert(back_edges, edge) + end + else + table.insert(cross_edges, edge) + end + end + end + + if #edges_to_traverse == 0 then + completed[node] = true + pop() + else + for i=#edges_to_traverse,1,-1 do + local neighbour = edges_to_traverse[i]:getNeighbour(node) + discovered[neighbour] = true + push(neighbour) + end + end + else + completed[node] = true + pop() + end + end + + return tree_and_forward_edges, cross_edges, back_edges +end + + + + + +-- +-- +-- Loops and Multiedges +-- +-- + + +--- Remove all loops from a graph +-- +-- This method will remove all loops from a graph. +-- +-- @param algorithm An algorithm object + +function Simplifiers:removeLoopsOldModel(algorithm) + local graph = algorithm.graph + local loops = {} + + for _,edge in ipairs(graph.edges) do + if edge:getHead() == edge:getTail() then + loops[#loops+1] = edge + end + end + + for i=1,#loops do + graph:deleteEdge(loops[i]) + end + + graph[algorithm].loops = loops +end + + + +--- Restore loops that were previously removed. +-- +-- @param algorithm An algorithm object + +function Simplifiers:restoreLoopsOldModel(algorithm) + local graph = algorithm.graph + + for _,edge in ipairs(graph[algorithm].loops) do + graph:addEdge(edge) + edge:getTail():addEdge(edge) + end + + graph[algorithm].loops = nil +end + + + + +--- Remove all multiedges. +-- +-- Every multiedge of the graph will be replaced by a single edge. +-- +-- @param algorithm An algorithm object + +function Simplifiers:collapseMultiedgesOldModel(algorithm, collapse_action) + local graph = algorithm.graph + local collapsed_edges = {} + local node_processed = {} + + for _,node in ipairs(graph.nodes) do + node_processed[node] = true + + local multiedge = {} + + local function handle_edge (edge) + + local neighbour = edge:getNeighbour(node) + + if not node_processed[neighbour] then + if not multiedge[neighbour] then + multiedge[neighbour] = Edge.new{ direction = Edge.RIGHT } + collapsed_edges[multiedge[neighbour]] = {} + end + + if collapse_action then + collapse_action(multiedge[neighbour], edge, graph) + end + + table.insert(collapsed_edges[multiedge[neighbour]], edge) + end + end + + for _,edge in ipairs(node:getIncomingEdges()) do + handle_edge(edge) + end + + for _,edge in ipairs(node:getOutgoingEdges()) do + handle_edge(edge) + end + + for neighbour, multiedge in pairs(multiedge) do + + if #collapsed_edges[multiedge] <= 1 then + collapsed_edges[multiedge] = nil + else + for _,subedge in ipairs(collapsed_edges[multiedge]) do + graph:deleteEdge(subedge) + end + + multiedge:addNode(node) + multiedge:addNode(neighbour) + + graph:addEdge(multiedge) + end + end + end + + graph[algorithm].collapsed_edges = collapsed_edges +end + + +--- Expand multiedges that were previously collapsed +-- +-- @param algorithm An algorithm object + +function Simplifiers:expandMultiedgesOldModel(algorithm) + local graph = algorithm.graph + for multiedge, subedges in pairs(graph[algorithm].collapsed_edges) do + assert(#subedges >= 2) + + graph:deleteEdge(multiedge) + + for _,edge in ipairs(subedges) do + + -- Copy bend points + for _,p in ipairs(multiedge.bend_points) do + edge.bend_points[#edge.bend_points+1] = p:copy() + end + + -- Copy options + for k,v in pairs(multiedge.algorithmically_generated_options) do + edge.algorithmically_generated_options[k] = v + end + + for _,node in ipairs(edge.nodes) do + node:addEdge(edge) + end + + graph:addEdge(edge) + end + end + + graph[algorithm].collapsed_edges = nil +end + + + + + +-- Done + +return Simplifiers diff --git a/graphics/pgf/base/tex/generic/graphdrawing/lua/pgf/gd/lib/Stack.lua b/graphics/pgf/base/tex/generic/graphdrawing/lua/pgf/gd/lib/Stack.lua new file mode 100644 index 0000000000..75084b6e38 --- /dev/null +++ b/graphics/pgf/base/tex/generic/graphdrawing/lua/pgf/gd/lib/Stack.lua @@ -0,0 +1,61 @@ +-- Copyright 2011 by Jannis Pohlmann +-- Copyright 2012 by Till Tantau +-- +-- This file may be distributed an/or modified +-- +-- 1. under the LaTeX Project Public License and/or +-- 2. under the GNU Public License +-- +-- See the file doc/generic/pgf/licenses/LICENSE for more information + +-- @release $Header$ + + +--- A Stack is a very simple wrapper around an array +-- +-- + +local Stack = {} +Stack.__index = Stack + + +-- Namespace +require("pgf.gd.lib").Stack = Stack + + +--- Create a new stack +function Stack.new() + local stack = {} + setmetatable(stack, Stack) + return stack +end + + +--- Push an element on top of the stack +function Stack:push(data) + self[#self+1] = data +end + + +--- Inspect (but not pop) the top element of a stack +function Stack:peek() + return self[#self] +end + + +--- Pop an element from the top of the stack +function Stack:pop() + return table.remove(self, #self) +end + + +--- Get the height of the stack +function Stack:getSize() + return #self +end + + + +-- done + +return Stack
\ No newline at end of file diff --git a/graphics/pgf/base/tex/generic/graphdrawing/lua/pgf/gd/lib/Storage.lua b/graphics/pgf/base/tex/generic/graphdrawing/lua/pgf/gd/lib/Storage.lua new file mode 100644 index 0000000000..e029fdf7ff --- /dev/null +++ b/graphics/pgf/base/tex/generic/graphdrawing/lua/pgf/gd/lib/Storage.lua @@ -0,0 +1,110 @@ +-- Copyright 2012 by Till Tantau +-- +-- This file may be distributed an/or modified +-- +-- 1. under the LaTeX Project Public License and/or +-- 2. under the GNU Public License +-- +-- See the file doc/generic/pgf/licenses/LICENSE for more information + +-- @release $Header$ + + + +--- +-- A storage is an object that, as the name suggests, allows you to +-- ``store stuff concerning objects.'' Basically, it behaves like +-- table having weak keys, which means that once the objects for which +-- you ``store stuff'' go out of scope, they are also removed from the +-- storage. Also, you can specify that for each object of the storage +-- you store a table. In this case, there is no need to initialize +-- this table for each object; rather, when you write into such a +-- table and it does not yet exist, it is created ``on the fly''. +-- +-- The typical way you use storages is best explained with the +-- following example: Suppose you want to write a depth-first search +-- algorithm for a graph. This algorithm might wish to mark all nodes +-- it has visited. It could just say |v.marked = true|, but this might +-- clash with someone else also using the |marked| key. The solution is +-- to create a |marked| storage. The algorithm can first say +--\begin{codeexample}[code only, tikz syntax=false] +--local marked = Storage.new() +--\end{codeexample} +-- and then say +--\begin{codeexample}[code only, tikz syntax=false] +--marked[v] = true +--\end{codeexample} +-- to mark its objects. The |marked| storage object does not need to +-- be created locally inside a function, you can declare it as a local +-- variable of the whole file; nevertheless, the entries for vertices +-- no longer in use get removed automatically. You can also make it a +-- member variable of the algorithm class, which allows you make the +-- information about which objects are marked globally +-- accessible. +-- +-- Now suppose the algorithm would like to store even more stuff in +-- the storage. For this, we might use a table and can use the fact +-- that a storage will automatically create a table when necessary: +--\begin{codeexample}[code only, tikz syntax=false] +--local info = Storage.newTableStorage() +-- +--info[v].marked = true -- the "info[v]" table is +-- -- created automatically here +-- +--info[v].foo = "bar" +--\end{codeexample} +-- Again, once |v| goes out of scope, both it and the info table will +-- removed. + +local Storage = {} + +-- Namespace +require("pgf.gd.lib").Storage = Storage + + +-- The simple metatable + +local SimpleStorageMetaTable = { __mode = "k" } + +-- The advanced metatable for table storages: + +local TableStorageMetaTable = { + __mode = "k", + __index = + function(t, k) + local new = {} + rawset(t, k, new) + return new + end +} + + +--- +-- Create a new storage object. +-- +-- @return A new |Storage| instance. + +function Storage.new() + return setmetatable({}, SimpleStorageMetaTable) +end + + +--- +-- Create a new storage object which will install a table for every +-- entry automatically. +-- +-- @return A new |Storage| instance. + +function Storage.newTableStorage() + return setmetatable({}, TableStorageMetaTable) +end + + + + + + + +-- Done + +return Storage diff --git a/graphics/pgf/base/tex/generic/graphdrawing/lua/pgf/gd/lib/Transform.lua b/graphics/pgf/base/tex/generic/graphdrawing/lua/pgf/gd/lib/Transform.lua new file mode 100644 index 0000000000..7695b1b239 --- /dev/null +++ b/graphics/pgf/base/tex/generic/graphdrawing/lua/pgf/gd/lib/Transform.lua @@ -0,0 +1,120 @@ +-- Copyright 2012 by Till Tantau +-- +-- This file may be distributed an/or modified +-- +-- 1. under the LaTeX Project Public License and/or +-- 2. under the GNU Public License +-- +-- See the file doc/generic/pgf/licenses/LICENSE for more information + +-- @release $Header$ + + +--- +-- The |Transform| table provides a set of static methods for +-- creating and handling canvas transformation matrices. Such a matrix +-- is actually just an array of six numbers. The idea is that +-- ``applying'' an array { a, b, c, d, e, f } a vector $(x,y)$ will +-- yield the new vector $(ax+by+e,cx+dy+f)$. For details on how such +-- matrices work, see Section~\ref{section-transform-cm} +-- +local Transform = {} + + +-- Namespace + +require("pgf.gd.model").Transform = Transform + + +--- Creates a new transformation array. +-- +-- @param a First component +-- @param b Second component +-- @param c Third component +-- @param d Fourth component +-- @param x The x shift +-- @param y The y shift +-- +-- @return A transformation object. +-- +function Transform.new(a,b,c,d,x,y) + return { a, b, c, d, x, y } +end + + +--- Creates a new transformation object that represents a shift. +-- +-- @param x An x-shift +-- @param y A y-shift +-- +-- @return A transformation object +-- +function Transform.new_shift(x,y) + return { 1, 0, 0, 1, x, y } +end + + +--- Creates a new transformation object that represents a rotation. +-- +-- @param angle An angle +-- +-- @return A transformation object +-- +function Transform.new_rotation(angle) + local c = math.cos(angle) + local s = math.sin(angle) + return { c, -s, s, c, 0, 0 } +end + + +--- Creates a new transformation object that represents a scaling. +-- +-- @param x The horizontal scaling +-- @param y The vertical scaling (if missing, the horizontal scaling is used) +-- +-- @return A transformation object +-- +function Transform.new_scaling(x_scale, y_scale) + return { x_scale, 0, 0, y_scale or x_scale, 0, 0 } +end + + + + +--- +-- Concatenate two transformation matrices, returning the new one. +-- +-- @param a The first transformation +-- @param b The second transformation +-- +-- @return The transformation representing first applying |b| and then +-- applying |a|. +-- +function Transform.concat(a,b) + local a1, a2, a3, a4, a5, a6, b1, b2, b3, b4, b5, b6 = + a[1], a[2], a[3], a[4], a[5], a[6], b[1], b[2], b[3], b[4], b[5], b[6] + return { a1*b1 + a2*b3, a1*b2 + a2*b4, + a3*b1 + a4*b3, a3*b2 + a4*b4, + a1*b5 + a2*b6 + a5, a3*b5 + a4*b6 + a6 } +end + + + +--- +-- Inverts a transformation matrix. +-- +-- @param t The transformation. +-- +-- @return The inverted transformation +-- +function Transform.invert(t) + local t1, t2, t3, t4 = t[1], t[2], t[3], t[4] + local idet = 1/(t1*t4 - t2*t3) + + return { t4*idet, -t2*idet, -t3*idet, t1*idet, -t[5], -t[6] } +end + + +-- Done + +return Transform diff --git a/graphics/pgf/base/tex/generic/graphdrawing/lua/pgf/gd/model.lua b/graphics/pgf/base/tex/generic/graphdrawing/lua/pgf/gd/model.lua new file mode 100644 index 0000000000..4a0130488b --- /dev/null +++ b/graphics/pgf/base/tex/generic/graphdrawing/lua/pgf/gd/model.lua @@ -0,0 +1,26 @@ +-- Copyright 2012 by Till Tantau +-- +-- This file may be distributed an/or modified +-- +-- 1. under the LaTeX Project Public License and/or +-- 2. under the GNU Public License +-- +-- See the file doc/generic/pgf/licenses/LICENSE for more information + +--- @release $Header$ + + + +-- Imports + +require "pgf" +require "pgf.gd" + + +-- Declare namespace +pgf.gd.model = {} + + +-- Done + +return pgf.gd.model
\ No newline at end of file diff --git a/graphics/pgf/base/tex/generic/graphdrawing/lua/pgf/gd/model/Arc.lua b/graphics/pgf/base/tex/generic/graphdrawing/lua/pgf/gd/model/Arc.lua new file mode 100644 index 0000000000..e007e9b59b --- /dev/null +++ b/graphics/pgf/base/tex/generic/graphdrawing/lua/pgf/gd/model/Arc.lua @@ -0,0 +1,653 @@ +-- Copyright 2012 by Till Tantau +-- +-- This file may be distributed an/or modified +-- +-- 1. under the LaTeX Project Public License and/or +-- 2. under the GNU Public License +-- +-- See the file doc/generic/pgf/licenses/LICENSE for more information + +-- @release $Header$ + + +--- +-- An arc is a light-weight object representing an arc from a vertex +-- in a graph to another vertex. You may not create an |Arc| by +-- yourself, which is why there is no |new| method, arc creation is +-- done by the Digraph class. +-- +-- Every arc belongs to exactly one graph. If you want the same arc in +-- another graph, you need to newly connect two vertices in the other graph. +-- +-- You may read the |head| and |tail| fields, but you may not write +-- them. In order to store data for an arc, use |Storage| objects. +-- +-- Between any two vertices of a graph there can be only one arc, so +-- all digraphs are always simple graphs. However, in the +-- specification of a graph (the syntactic digraph), there might +-- be multiple edges between two vertices. This means, in particular, +-- that an arc has no |options| field. Rather, it has several +-- |optionsXxxx| functions, that will search for options in all of the +-- syntactic edges that ``belong'' to an edge. +-- +-- In order to \emph{set} options of the edges, you can set the +-- |generated_options| field of an arc (which is |nil| by default), see +-- the |declare_parameter_sequence| function for the syntax. Similar +-- to the |path| field below, the options set in this table are +-- written back to the syntactic edges during a sync. +-- +-- Finally, there is also an |animations| field, which, similarly to +-- the |generated_options|, gets written back during a sync when it is +-- not |nil|. +-- +-- In detail, the following happens: Even though an arc has a |path|, +-- |generated_options|, and |animations| fields, setting these fields does +-- not immediately set the paths of the syntactic edges nor does it +-- generate options. Indeed, you will normally want to setup and +-- modify the |path| field of an arc during your algorithm and only at +-- the very end, ``write it back'' to the multiple syntactic edges +-- underlying the graph. For this purpose, the method |sync| is used, +-- which is called automatically for the |ugraph| and |digraph| of a +-- scope as well as for spanning trees. +-- +-- The bottom line concerning the |path| field is the following: If +-- you just want a straight line along an arc, just leave the field as +-- it is (namely, |nil|). If you want to have all edges along a path +-- to follow a certain path, set the |path| field of the arc to the +-- path you desire (typically, using the |setPolylinePath| or a +-- similar method). This will cause all syntactic edges underlying the +-- arc to be set to the specified path. In the event that you want to +-- set different paths for the edges underlying a single arc +-- differently, set the |path| fields of these edges and set the +-- |path| field of the arc to |nil|. This will disable the syncing for +-- the arc and will cause the edge |paths| to remain untouched. +-- +-- @field tail The tail vertex of the arc. +-- @field head The head vertex of the arc. May be the same as the tail +-- in case of a loop. +-- @field path If non-nil, the path of the arc. See the description +-- above. +-- @field generated_options If non-nil, some options to be passed back +-- to the original syntactic edges, see the description above. +-- @field animations If non-nil, some animations to be passed back +-- to the original syntactic edges. See the description of the +-- |animations| field for |Vertex| for details on the syntax. +-- @field syntactic_edges In case this arc is an arc in the syntactic +-- digraph (and only then), this field contains an array containing +-- syntactic edges (``real'' edges in the syntactic digraph) that +-- underly this arc. Otherwise, the field will be empty or |nil|. +-- +local Arc = {} +Arc.__index = Arc + + +-- Namespace + +require("pgf.gd.model").Arc = Arc + + +-- Imports + +local Path = require 'pgf.gd.model.Path' +local lib = require 'pgf.gd.lib' + + +--- +-- Get an array of options of the syntactic edges corresponding to an arc. +-- +-- An arc in a digraph is typically (but not always) present because +-- there are one or more edges in the syntactic digraph between the +-- tail and the head of the arc or between the head and the tail. +-- +-- Since for every arc there can be several edges present in the +-- syntactic digraph, an option like |length| may have +-- been given multiple times for the edges corresponding to the arc. +-- +-- If your algorithm gets confused by multiple edges, try saying +-- |a:options(your_option)|. This will always give the ``most +-- sensible'' choice of the option if there are multiple edges +-- corresponding to the same arc. +-- +-- @param option A string option like |"length"|. +-- +-- @return A table with the following contents: +-- % +-- \begin{enumerate} +-- \item It is an array of all values the option has for edges +-- corresponding to |self| in the syntactic digraph. Suppose, for +-- instance, you write the following: +-- % +--\begin{codeexample}[code only] +--graph { +-- tail -- [length=1] head, % multi edge 1 +-- tail -- [length=3] head, % mulit edge 2 +-- head -- [length=8] tail, % multi edge 3 +-- tail -- head, % multi edge 4 +-- head -- [length=7] tail, % multi edge 5 +-- tail -- [length=2] head, % multi edge 6 +--} +--\end{codeexample} +-- % +-- Suppose, furthermore, that |length| has been setup as an edge +-- option. Now suppose that |a| is the arc from the vertex |tail| to +-- the vertex |head|. Calling |a:optionsArray('length')| will +-- yield the array part |{1,3,2,8,7}|. The reason for the ordering is +-- as follows: First come all values |length| had for syntactic edges +-- going from |self.tail| to |self.head| in the order they appear in the +-- graph description. Then come all values the options has for syntactic +-- edges going from |self.head| to |self.tail|. The reason for this +-- slightly strange behavior is that many algorithms do not really +-- care whether someone writes |a --[length=1] b| or +-- |b --[length=1] a|; in both cases they would ``just'' like to know +-- that the length is~|1|. +-- +-- \item There is field called |aligned|, which is an array storing +-- the actual syntactic edge objects whose values can be found in the +-- array part of the returned table. However, |aligned| contains only +-- the syntactic edges pointing ``in the same direction'' as the arc, +-- that is, the tail and head of the syntactic edge are the same as +-- those of the arc. In the above example, this array would contain +-- the edges with the comment numbers |1|, |2|, and |6|. +-- +-- Using the length of this array and the fact that the ``aligned'' +-- values come first in the table, you can easily iterate over the +-- |option|'s values of only those edges that are aligned with the arc: +-- % +--\begin{codeexample}[code only, tikz syntax=false] +--local a = g:arc(tail.head) -- some arc +--local opt = a:optionsArray('length') +--local sum = 0 +--for i=1,#opt.aligned do +-- sum = sum + opt[i] +--end +--\end{codeexample} +-- % +-- \item There is a field called |anti_aligned|, which is an array +-- containing exactly the edges in the array part of the table not +-- aligned with the arc. The numbering start at |1| as usual, so the +-- $i$th entry of this table corresponds to the entry at position $i + +-- \verb!#opt.aligned!$ of the table. +-- \end{enumerate} +-- +function Arc:optionsArray(option) + + local cache = self.option_cache + local t = cache[option] + if t then + return t + end + + -- Accumulate the edges for which the option is set: + local tail = self.tail + local head = self.head + local s_graph = self.syntactic_digraph + + local arc = s_graph:arc(tail, head) + local aligned = {} + if arc then + for _,m in ipairs(arc.syntactic_edges) do + if m.options[option] ~= nil then + aligned[#aligned + 1] = m + end + end + table.sort(aligned, function (a,b) return a.event.index < b.event.index end) + end + + local arc = head ~= tail and s_graph:arc(head, tail) + local anti_aligned = {} + if arc then + for _,m in ipairs(arc.syntactic_edges) do + if m.options[option] ~= nil then + anti_aligned[#anti_aligned + 1] = m + end + end + table.sort(anti_aligned, function (a,b) return a.event.index < b.event.index end) + end + + -- Now merge them together + local t = { aligned = aligned, anti_aligned = anti_aligned } + for i=1,#aligned do + t[i] = aligned[i].options[option] + end + for i=1,#anti_aligned do + t[#t+1] = anti_aligned[i].options[option] + end + cache[option] = t + + return t +end + + + +--- +-- Returns the first option, that is, the first entry of +-- |Arc:optionsArray(option)|. However, if the |only_aligned| +-- parameter is set to true and there is no option with any aligned +-- syntactic edge, |nil| is returned. +-- +-- @param option An option +-- @param only_aligned If true, only aligned syntactic edges will be +-- considered. +-- @return The first entry of the |optionsArray| +function Arc:options(option, only_aligned) + if only_aligned then + local opt = self:optionsArray(option) + if #opt.aligned > 0 then + return opt[1] + end + else + return self:optionsArray(option)[1] + end +end + + + + +--- +-- Get an accumulated value of an option of the syntactic edges +-- corresponding to an arc. +-- +-- @param option The option of interest +-- @param accumulator A function taking two values. When there are +-- more than one syntactic edges corresponding to |self| for which the +-- |option| is set, this function will be called repeatedly for the +-- different values. The first time it will be called for the first +-- two values. Next, it will be called for the result of this call and +-- the third value, and so on. +-- @param only_aligned A boolean. If true, only the aligned syntactic +-- edges will be considered. +-- +-- @return If the option is not set for any (aligned) syntactic edges +-- corresponding to |self|, |nil| is returned. If there is exactly one +-- edge, the value of this edge is returned. Otherwise, the result of +-- repeatedly applying the |accumulator| function as described +-- above. +-- +-- The result is cached, repeated calls will not invoke the +-- |accumulator| function again. +-- +-- @usage Here is typical usage: +-- % +--\begin{codeexample}[code only, tikz syntax=false] +--local total_length = a:optionsAccumulated('length', function (a,b) return a+b end) or 0 +--\end{codeexample} +-- +function Arc:optionsAccumulated(option, accumulator, only_aligned) + local opt = self:options(option) + if only_aligned then + local aligned = opt.aligned + local v = aligned[accumulator] + if v == nil then + v = opt[1] + for i=2,#aligned do + v = accumulator(v, opt[i]) + end + align[accumulator] = v + end + return v + else + local v = opt[accumulator] + if v == nil then + v = opt[1] + for i=2,#opt do + v = accumulator(v, opt[i]) + end + opt[accumulator] = v + end + return v + end +end + + + +--- +-- Compute the syntactic head and tail of an arc. For this, we have a +-- look at the syntactic digraph underlying the arc. If there is at +-- least once syntactic edge going from the arc's tail to the arc's +-- head, the arc's tail and head are returned. Otherwise, we test +-- whether there is a syntactic edge in the other direction and, if +-- so, return head and tail in reverse order. Finally, if there is no +-- syntactic edge at all corresponding to the arc in either direction, +-- |nil| is returned. +-- +-- @return The syntactic tail +-- @return The syntactic head + +function Arc:syntacticTailAndHead () + local s_graph = self.syntactic_digraph + local tail = self.tail + local head = self.head + if s_graph:arc(tail, head) then + return tail, head + elseif s_graph:arc(head, tail) then + return head, tail + end +end + + +--- +-- Compute the point cloud. +-- +-- @return This method will return the ``point cloud'' of an arc, +-- which is an array of all points that must be rotated and shifted +-- along with the endpoints of an edge. +-- +function Arc:pointCloud () + if self.cached_point_cloud then + return self.cached_point_cloud -- cached + end + local cloud = {} + local a = self.syntactic_digraph:arc(self.tail,self.head) + if a then + for _,e in ipairs(a.syntactic_edges) do + for _,p in ipairs(e.path) do + if type(p) == "table" then + cloud[#cloud + 1] = p + end + end + end + end + self.cached_point_cloud = cloud + return cloud +end + + + +--- +-- Compute an event index for the arc. +-- +-- @return The lowest event index of any edge involved +-- in the arc (or nil, if there is no syntactic edge). +-- +function Arc:eventIndex () + if self.cached_event_index then + return self.cached_event_index + end + local head = self.head + local tail = self.tail + local e = math.huge + local a = self.syntactic_digraph:arc(tail,head) + if a then + for _,m in ipairs(a.syntactic_edges) do + e = math.min(e, m.event.index) + end + end + local a = head ~= tail and self.syntactic_digraph:arc(head,tail) + if a then + for _,m in ipairs(a.syntactic_edges) do + e = math.min(e, m.event.index) + end + end + self.cached_event_index = e + return e +end + + + + +--- +-- The span collector +-- +-- This method returns the top (that is, smallest) priority of any +-- edge involved in the arc. +-- +-- The priority of an edge is computed as follows: +-- % +-- \begin{enumerate} +-- \item If the option |"span priority"| is set, this number +-- will be used. +-- \item If the edge has the same head as the arc, we lookup the key\\ +-- |"span priority " .. edge.direction|. If set, we use this value. +-- \item If the edge has a different head from the arc (the arc is +-- ``reversed'' with respect to the syntactic edge), we lookup the key +-- |"span priority reversed " .. edge.direction|. If set, we use this value. +-- \item Otherwise, we use priority 5. +-- \end{enumerate} +-- +-- @return The priority of the arc, as described above. +-- +function Arc:spanPriority() + if self.cached_span_priority then + return self.cached_span_priority + end + + local head = self.head + local tail = self.tail + local min + local g = self.syntactic_digraph + + local a = g:arc(tail,head) + if a then + for _,m in ipairs(a.syntactic_edges) do + local p = + m.options["span priority"] or + lib.lookup_option("span priority " .. m.direction, m, g) + + min = math.min(p or 5, min or math.huge) + end + end + + local a = head ~= tail and g:arc(head,tail) + if a then + for _,m in ipairs(a.syntactic_edges) do + local p = + m.options["span priority"] or + lib.lookup_option("span priority reversed " .. m.direction, m, g) + + min = math.min(p or 5, min or math.huge) + end + end + + self.cached_span_priority = min or 5 + + return min or 5 +end + + + + + + +--- +-- Sync an |Arc| with its syntactic edges with respect to the path and +-- generated options. It causes the following to happen: +-- If the |path| field of the arc is |nil|, nothing +-- happens with respect to the path. Otherwise, a copy of the |path| +-- is created. However, for every path element that is a function, +-- this function is invoked with the syntactic edge as its +-- parameter. The result of this call should now be a |Coordinate|, +-- which will replace the function in the |Path|. +-- +-- You use this method like this: +-- % +--\begin{codeexample}[code only, tikz syntax=false] +--... +--local arc = g:connect(s,t) +--arc:setPolylinePath { Coordinate.new(x,y), Coordinate.new(x1,y1) } +--... +--arc:sync() +--\end{codeexample} +-- +-- Next, similar to the path, the field |generated_options| is +-- considered. If it is not |nil|, then all options listed in this +-- field are appended to all syntactic edges underlying the arc. +-- +-- Note that this function will automatically be called for all arcs +-- of the |ugraph|, the |digraph|, and the |spanning_tree| of an +-- algorithm by the rendering pipeline. +-- +function Arc:sync() + if self.path then + local path = self.path + local head = self.head + local tail = self.tail + local a = self.syntactic_digraph:arc(tail,head) + if a and #a.syntactic_edges>0 then + for _,e in ipairs(a.syntactic_edges) do + local clone = path:clone() + for i=1,#clone do + local p = clone[i] + if type(p) == "function" then + clone[i] = p(e) + if type(clone[i]) == "table" then + clone[i] = clone[i]:clone() + end + end + end + e.path = clone + end + end + local a = head ~= tail and self.syntactic_digraph:arc(head,tail) + if a and #a.syntactic_edges>0 then + for _,e in ipairs(a.syntactic_edges) do + local clone = path:reversed() + for i=1,#clone do + local p = clone[i] + if type(p) == "function" then + clone[i] = p(e) + if type(clone[i]) == "table" then + clone[i] = clone[i]:clone() + end + end + end + e.path = clone + end + end + end + if self.generated_options then + local head = self.head + local tail = self.tail + local a = self.syntactic_digraph:arc(tail,head) + if a and #a.syntactic_edges>0 then + for _,e in ipairs(a.syntactic_edges) do + for _,o in ipairs(self.generated_options) do + e.generated_options[#e.generated_options+1] = o + end + end + end + local a = head ~= tail and self.syntactic_digraph:arc(head,tail) + if a and #a.syntactic_edges>0 then + for _,e in ipairs(a.syntactic_edges) do + for _,o in ipairs(self.generated_options) do + e.generated_options[#e.generated_options+1] = o + end + end + end + end + if self.animations then + local head = self.head + local tail = self.tail + local a = self.syntactic_digraph:arc(tail,head) + if a and #a.syntactic_edges>0 then + for _,e in ipairs(a.syntactic_edges) do + for _,o in ipairs(self.animations) do + e.animations[#e.animations+1] = o + end + end + end + local a = head ~= tail and self.syntactic_digraph:arc(head,tail) + if a and #a.syntactic_edges>0 then + for _,e in ipairs(a.syntactic_edges) do + for _,o in ipairs(self.animations) do + e.animations[#e.animations+1] = o + end + end + end + end +end + + +--- +-- This method returns a ``coordinate factory'' that can be used as +-- the coordinate of a |moveto| at the beginning of a path starting at +-- the |tail| of the arc. Suppose you want to create a path starting +-- at the tail vertex, going to the coordinate $(10,10)$ and ending at +-- the head vertex. The trouble is that when you create the path +-- corresponding to this route, you typically do not know where the +-- tail vertex is going to be. Even if that \emph{has} already been +-- settled, you will still have the problem that different edges +-- underlying the arc may wish to start their paths at different +-- anchors inside the tail vertex. In such cases, you use this +-- method to get a function that will, later on, compute the correct +-- position of the anchor as needed. +-- +-- Here is the code you would use to create the above-mentioned path: +-- % +--\begin{codeexample}[code only, tikz syntax=false] +--local a = g:connect(tail,head) +--... +--arc.path = Path.new() +--arc.path:appendMoveto(arc:tailAnchorForArcPath()) +--arc.path:appendLineto(10, 10) +--arc.path:appendLineto(arc:headAnchorForArcPath()) +--\end{codeexample} +-- +-- Normally, however, you will not write code as detailed as the above +-- and you would just write instead of the last three lines: +-- % +--\begin{codeexample}[code only, tikz syntax=false] +--arc:setPolylinePath { Coordinate.new (10, 10) } +--\end{codeexample} + +function Arc:tailAnchorForArcPath() + return function (edge) + local a = edge.options['tail anchor'] + if a == "" then + a = "center" + end + return self.tail:anchor(a) + self.tail.pos + end +end + +--- +-- See |Arc:tailAnchorForArcPath|. + +function Arc:headAnchorForArcPath() + return function (edge) + local a = edge.options['head anchor'] + if a == "" then + a = "center" + end + return self.head:anchor(a) + self.head.pos + end +end + + + +--- +-- Setup the |path| field of an arc in such a way that it corresponds +-- to a sequence of straight line segments starting at the tail's +-- anchor and ending at the head's anchor. +-- +-- @param coordinates An array of |Coordinates| through which the line +-- will go through. + +function Arc:setPolylinePath(coordinates) + local p = Path.new () + + p:appendMoveto(self:tailAnchorForArcPath()) + + for _,c in ipairs(coordinates) do + p:appendLineto(c) + end + + p:appendLineto(self:headAnchorForArcPath()) + + self.path = p +end + + + + +-- Returns a string representation of an arc. This is mainly for debugging +-- +-- @return The Arc as string. +-- +function Arc:__tostring() + return tostring(self.tail) .. "->" .. tostring(self.head) +end + + +-- Done + +return Arc diff --git a/graphics/pgf/base/tex/generic/graphdrawing/lua/pgf/gd/model/Collection.lua b/graphics/pgf/base/tex/generic/graphdrawing/lua/pgf/gd/model/Collection.lua new file mode 100644 index 0000000000..abc454b7db --- /dev/null +++ b/graphics/pgf/base/tex/generic/graphdrawing/lua/pgf/gd/model/Collection.lua @@ -0,0 +1,208 @@ +-- Copyright 2012 by Till Tantau +-- +-- This file may be distributed an/or modified +-- +-- 1. under the LaTeX Project Public License and/or +-- 2. under the GNU Public License +-- +-- See the file doc/generic/pgf/licenses/LICENSE for more information + +-- @release $Header$ + + +--- +-- A collection is essentially a subgraph of a graph, that is, a +-- ``collection'' of some nodes and some edges of the graph. The name +-- ``collection'' was chosen over ``subgraph'' since the latter are +-- often thought of as parts of a graph that are rendered in a special +-- way (such as being surrounded by a rectangle), while collections +-- are used to model such diverse things as hyperedges, sets of +-- vertices that should be on the same level in a layered algorithm, +-- or, indeed, subgraphs that are rendered in a special way. +-- +-- Collections are grouped into ``kinds''. All collections of a given +-- kind can be accessed by algorithms through an array whose elements +-- are the collections. On the display layer, for each kind a separate +-- key is available to indicate that a node or an edge belongs to a +-- collection. +-- +-- Collections serve two purposes: First, they can be seen as ``hints'' +-- to graph drawing algorithms that certain nodes and/or edges ``belong +-- together''. For instance, collections of kind |same layer| are used +-- by the Sugiyama algorithm to group together nodes that should appear +-- at the same height of the output. Second, since collections are also +-- passed back to the display layer in a postprocessing step, they can be +-- used to render complicated concepts such as hyperedges (which are +-- just collections of nodes, after all) or subgraphs. +-- +-- @field kind The ``kind'' of the collection. +-- +-- @field vertices A lookup table of vertices (that is, both an array +-- with the vertices in the order in which they appear as well as a +-- table such that |vertices[vertex] == true| whenever |vertex| is +-- present in the table. +-- +-- @field edges A lookup table of edges (not arcs!). +-- +-- @field options An options table. This is the table of options that +-- was in force when the collection was created. +-- +-- @field child_collections An array of all collections that are +-- direct children of this collection (that is, +-- they were defined while the current collection was the most +-- recently defined collection on the options stack). However, you +-- should use the methods |children|, |descendants|, and so to access +-- this field. +-- +-- @field parent_collection The parent collection of the current +-- collection. This field may be |nil| in case a collection has no parent. +-- +-- @field event An |Event| object that was create for this +-- collection. Its |kind| will be |"collection"| while its |parameter| +-- will be the collection kind. + +local Collection = {} +Collection.__index = Collection + + +-- Namespace + +require("pgf.gd.model").Collection = Collection + + +-- Imports +local Storage = require "pgf.gd.lib.Storage" + + + +--- +-- Creates a new collection. You should not call this function +-- directly, it is called by the interface classes. +-- +-- @param t A table of initial values. The field |t.kind| must be a +-- nonempty string. +-- +-- @return The new collection +-- +function Collection.new(t) + assert (type(t.kind) == "string" and t.kind ~= "", "collection kind not set") + + return setmetatable( + { + vertices = t.vertices or {}, + edges = t.edges or {}, + options = t.options or {}, + generated_options = t.generated_options or {}, + kind = t.kind, + event = t.event, + child_collections = t.child_collections or {}, + }, Collection) +end + + + + +-- +-- An internal function for registering a collection as child of +-- another collection. The collection |self| will be made a child +-- collection of |parent|. +-- +-- @param parent A collection. + +function Collection:registerAsChildOf(parent) + self.parent = parent + if parent then + assert (getmetatable(parent) == Collection, "parent must be a collection") + parent.child_collections[#parent.child_collections+1] = self + end +end + + + +--- +-- A collection can have any number of \emph{child collections}, which +-- are collections nested inside the collection. You can access the +-- array of these children through this method. You may not modify +-- the array returned by this function. +-- +-- @return The array of children of |self|. +-- +function Collection:children() + return self.child_collections +end + + +--- +-- This method works like the |children| method. However, the tree of +-- collections is, conceptually, contracted by considering only these +-- collections that have the |kind| given as parameter. For instance, +-- if |self| has a child collection of a kind different from |kind|, +-- but this child collection has, in turn, a child collection of kind +-- |kind|, this latter child collection will be included in the array +-- -- but not any of its child collections. +-- +-- @param kind The collection kind to which the tree of collections +-- should be restricted. +-- +-- @return The array of children of |self| in this contracted tree. +-- +function Collection:childrenOfKind(kind) + local function rec (c, a) + for _,d in ipairs(c.child_collections) do + if d.kind == kind then + a[#a + 1] = d + else + rec (d, a) + end + end + return a + end + return rec(self, {}) +end + + +--- +-- The descendants of a collection are its children, plus their +-- children, plus their children, and so on. +-- +-- @return An array of all descendants of |self|. It will be in +-- preorder. + +function Collection:descendants() + local function rec (c, a) + for _,d in ipairs(c.child_collections) do + a[#a + 1] = d + rec (d, a) + end + return a + end + return rec(self, {}) +end + + + +--- +-- The descendants of a collection of the given |kind|. +-- +-- @param kind A collection kind. +-- +-- @return An array of all descendants of |self| of the given |kind|. + +function Collection:descendantsOfKind(kind) + local function rec (c, a) + for _,d in ipairs(c.child_collections) do + if d.kind == kind then + a[#a + 1] = d + end + rec (d, a) + end + return a + end + return rec(self, {}) +end + + + +-- Done + +return Collection diff --git a/graphics/pgf/base/tex/generic/graphdrawing/lua/pgf/gd/model/Coordinate.lua b/graphics/pgf/base/tex/generic/graphdrawing/lua/pgf/gd/model/Coordinate.lua new file mode 100644 index 0000000000..3a4448ab08 --- /dev/null +++ b/graphics/pgf/base/tex/generic/graphdrawing/lua/pgf/gd/model/Coordinate.lua @@ -0,0 +1,306 @@ +-- Copyright 2012 by Till Tantau +-- +-- This file may be distributed an/or modified +-- +-- 1. under the LaTeX Project Public License and/or +-- 2. under the GNU Public License +-- +-- See the file doc/generic/pgf/licenses/LICENSE for more information + +-- @release $Header$ + + +--- +-- A Coordinate models a position on the drawing canvas. +-- +-- It has an |x| field and a |y| field, which are numbers that will be +-- interpreted as \TeX\ points (1/72.27th of an inch). The $x$-axis goes +-- right and the $y$-axis goes up. +-- +-- @field x +-- @field y +-- +-- There is also a static field called |origin| that is always equal to the origin. + +local Coordinate = {} +Coordinate.__index = Coordinate + + +-- Namespace + +require("pgf.gd.model").Coordinate = Coordinate + + + + +--- Creates a new coordinate. +-- +-- @param x The $x$ value +-- @param y The $y$ value +-- +-- @return A coordinate +-- +function Coordinate.new(x,y) + return setmetatable( {x=x, y=y}, Coordinate) +end + + +Coordinate.origin = Coordinate.new(0,0) + + +--- Creates a new coordinate that is a copy of an existing one. +-- +-- @return A new coordinate at the same location as |self| +-- +function Coordinate:clone() + return setmetatable( { x = self.x, y = self.y }, Coordinate) +end + + + +--- Apply a transformation matrix to a coordinate, +-- see |pgf.gd.lib.Transform| for details. +-- +-- @param t A transformation. + +function Coordinate:apply(t) + local x = self.x + local y = self.y + self.x = t[1]*x + t[2]*y + t[5] + self.y = t[3]*x + t[4]*y + t[6] +end + + +--- Shift a coordinate +-- +-- @param a An $x$ offset +-- @param b A $y$ offset + +function Coordinate:shift(a,b) + self.x = self.x + a + self.y = self.y + b +end + + +--- +-- ``Unshift'' a coordinate (which is the same as shifting by the +-- inversed coordinate; only faster). +-- +-- @param a An $x$ offset +-- @param b A $y$ offset + +function Coordinate:unshift(a,b) + self.x = self.x - a + self.y = self.y - b +end + + +--- +-- Like |shift|, only for coordinate parameters. +-- +-- @param c Another coordinate. The $x$- and $y$-values of |self| are +-- increased by the $x$- and $y$-values of this coordinate. + +function Coordinate:shiftByCoordinate(c) + self.x = self.x + c.x + self.y = self.y + c.y +end + + +--- +-- Like |unshift|, only for coordinate parameters. +-- +-- @param c Another coordinate. + +function Coordinate:unshiftByCoordinate(c) + self.x = self.x - c.x + self.y = self.y - c.y +end + + +--- +-- Moves the coordinate a fraction of |f| along a straight line to |c|. +-- +-- @param c Another coordinate +-- @param f A fraction + +function Coordinate:moveTowards(c,f) + self.x = self.x + f*(c.x-self.x) + self.y = self.y + f*(c.y-self.y) +end + + + +--- Scale a coordinate by a factor +-- +-- @param s A factor. + +function Coordinate:scale(s) + self.x = s*self.x + self.y = s*self.y +end + + + + +--- +-- Add two coordinates, yielding a new coordinate. Note that it will +-- be a lot faster to call shift, whenever this is possible. +-- +-- @param a A coordinate +-- @param b A coordinate + +function Coordinate.__add(a,b) + return setmetatable({ x = a.x + b.x, y = a.y + b.y }, Coordinate) +end + + +--- +-- Subtract two coordinates, yielding a new coordinate. Note that it will +-- be a lot faster to call unshift, whenever this is possible. +-- +-- @param a A coordinate +-- @param b A coordinate + +function Coordinate.__sub(a,b) + return setmetatable({ x = a.x - b.x, y = a.y - b.y }, Coordinate) +end + + +--- +-- The unary minus (mirror the coordinate against the origin). +-- +-- @param a A coordinate + +function Coordinate.__unm(a) + return setmetatable({ x = - a.x, y = - a.y }, Coordinate) +end + + +--- +-- The multiplication operator. Its effect depends on the parameters: +-- If both are coordinates, their dot-product is returned. If exactly +-- one of them is a coordinate and the other is a number, the scalar +-- multiple of this coordinate is returned. +-- +-- @param a A coordinate or a scalar +-- @param b A coordinate or a scalar +-- @return The dot product or scalar product. + +function Coordinate.__mul(a,b) + if getmetatable(a) == Coordinate then + if getmetatable(b) == Coordinate then + return a.x * b.x + a.y * b.y + else + return setmetatable({ x = a.x * b, y = a.y *b }, Coordinate) + end + else + return setmetatable({ x = a * b.x, y = a * b.y }, Coordinate) + end +end + +--- +-- The division operator. Returns the scalar division of a coordinate +-- by a scalar. +-- +-- @param a A coordinate +-- @param b A scalar (not equal to zero). +-- @return The scalar product or a * (1/b). + +function Coordinate.__div(a,b) + return setmetatable({ x = a.x / b, y = a.y / b }, Coordinate) +end + + +--- +-- The norm function. Returns the norm of a coordinate. +-- +-- @param a A coordinate +-- @return The norm of the coordinate + +function Coordinate:norm() + return math.sqrt(self.x * self.x + self.y * self.y) +end + + +--- +-- Normalize a vector: Ensure that it has length 1. If the vector used +-- to be the 0-vector, it gets replaced by (1,0). +-- + +function Coordinate:normalize() + local x, y = self.x, self.y + if x == 0 and y == 0 then + self.x = 1 + else + local norm = math.sqrt(x*x+y*y) + self.x = x / norm + self.y = y / norm + end +end + + +--- +-- Normalized version of a vector: Like |normalize|, only the result is +-- returned in a new vector. +-- +-- @return Normalized version of |self| + +function Coordinate:normalized() + local x, y = self.x, self.y + if x == 0 and y == 0 then + return setmetatable({ x = 1, y = 0 }, Coordinate) + else + local norm = math.sqrt(x*x+y*y) + return setmetatable({ x = x/norm, y = y/norm }, Coordinate) + end +end + + + +--- +-- Compute a bounding box around an array of coordinates +-- +-- @param array An array of coordinates +-- +-- @return |min_x| The minimum $x$ value of the bounding box of the array +-- @return |min_y| The minimum $y$ value +-- @return |max_x| +-- @return |max_y| +-- @return |center_x| The center of the bounding box +-- @return |center_y| + +function Coordinate.boundingBox(array) + if #array > 0 then + local min_x, min_y = math.huge, math.huge + local max_x, max_y = -math.huge, -math.huge + + for i=1,#array do + local c = array[i] + local x = c.x + local y = c.y + if x < min_x then min_x = x end + if y < min_y then min_y = y end + if x > max_x then max_x = x end + if y > max_y then max_y = y end + end + + return min_x, min_y, max_x, max_y, (min_x+max_x) / 2, (min_y+max_y) / 2 + end +end + + + + +-- Returns a string representation of an arc. This is mainly for debugging +-- +-- @return The Arc as string. +-- +function Coordinate:__tostring() + return "(" .. self.x .. "pt," .. self.y .. "pt)" +end + + +-- Done + +return Coordinate
\ No newline at end of file diff --git a/graphics/pgf/base/tex/generic/graphdrawing/lua/pgf/gd/model/Digraph.lua b/graphics/pgf/base/tex/generic/graphdrawing/lua/pgf/gd/model/Digraph.lua new file mode 100644 index 0000000000..30b600e308 --- /dev/null +++ b/graphics/pgf/base/tex/generic/graphdrawing/lua/pgf/gd/model/Digraph.lua @@ -0,0 +1,849 @@ +-- Copyright 2012 by Till Tantau +-- +-- This file may be distributed an/or modified +-- +-- 1. under the LaTeX Project Public License and/or +-- 2. under the GNU Public License +-- +-- See the file doc/generic/pgf/licenses/LICENSE for more information + +-- @release $Header$ + + + + +--- +-- Each |Digraph| instance models a \emph{directed, simple} +-- graph. ``Directed'' means that all edges ``point'' from a head node +-- to a tail node. ``Simple'' means that between any nodes there can be +-- (at most) one edge. Since these properties are a bit at odds with +-- the normal behavior of ``nodes'' and ``edges'' in \tikzname, +-- different names are used for them inside the |model| namespace: +-- The class modeling ``edges'' is actually called |Arc| to stress +-- that an arc has a specific ``start'' (the tail) and a specific +-- ``end'' (the head). The class modeling ``nodes'' is actually called +-- |Vertex|, just to stress that this is not a direct model of a +-- \tikzname\ |node|, but can represent a arbitrary vertex of a graph, +-- independently of whether it is an actual |node| in \tikzname. +-- +-- \medskip +-- \noindent\emph{Time Bounds.} +-- Since digraphs are constantly created and modified inside the graph +-- drawing engine, some care was taken to ensure that all operations +-- work as quickly as possible. In particular: +-- % +-- \begin{itemize} +-- \item Adding an array of $k$ vertices using the |add| method needs +-- time $O(k)$. +-- \item Adding an arc between two vertices needs time $O(1)$. +-- \item Accessing both the |vertices| and the |arcs| fields takes time +-- $O(1)$, provided only the above operations are used. +-- \end{itemize} +-- % +-- Deleting vertices and arcs takes more time: +-- % +-- \begin{itemize} +-- \item Deleting the vertices given in an array of $k$ vertices from a +-- graph with $n$ vertices takes time $O(\max\{n,c\})$ where $c$ is the +-- number of arcs between the to-be-deleted nodes and the remaining +-- nodes. Note that this time bound in independent of~$k$. In +-- particular, it will be much faster to delete many vertices by once +-- calling the |remove| function instead of calling it repeatedly. +-- \item Deleting an arc takes time $O(t_o+h_i)$ where $t_o$ is the +-- number of outgoing arcs at the arc's tail and $h_i$ is the number +-- of incoming arcs at the arc's head. After a call to |disconnect|, +-- the next use of the |arcs| field will take time $O(|V| + |E|)$, +-- while subsequent accesses take time $O(1)$ -- till the +-- next use of |disconnect|. This means that once you start deleting +-- arcs using |disconnect|, you should perform as many additional +-- |disconnect|s as possible before accessing |arcs| one more. +-- \end{itemize} +-- +-- \medskip +-- \noindent\emph{Stability.} The |vertices| field and the array +-- returned by |Digraph:incoming| and |Digraph:outgoing| are +-- \emph{stable} in the following sense: The ordering of the elements +-- when you use |ipairs| on the will be the ordering in which the +-- vertices or arcs were added to the graph. Even when you remove a +-- vertex or an arc, the ordering of the remaining elements stays the +-- same. +-- +-- @field vertices This array contains the vertices that are part of +-- the digraph. Internally, this array +-- is an object of type |LookupTable|, but you can mostly treat it as +-- if it were an array. In particular, you can iterate over its +-- elements using |ipairs|, but you may not modify the array; use the +-- |add| and |remove| methods, instead. +-- +-- \begin{codeexample}[code only, tikz syntax=false] +-- local g = Digraph.new {} +-- +-- g:add { v1, v2 } -- Add vertices v1 and v2 +-- g:remove { v2 } -- Get rid of v2. +-- +-- assert (g:contains(v1)) +-- assert (not g:contains(v2)) +-- \end{codeexample} +-- +-- It is important to note that although each digraph stores a +-- |vertices| array, the elements in this array are not exclusive to +-- the digraph: A vertex can be an element of any number of +-- digraphs. Whether or not a vertex is an element of digraph is not +-- stored in the vertex, only in the |vertices| array of the +-- digraph. To test whether a digraph contains a specific node, use the +-- |contains| method, which takes time $O(1)$ to perform the test (this +-- is because, as mentioned earlier, the |vertices| array is actually a +-- |LookupTable| and for each vertex |v| the field |vertices[v]| will +-- be true if, and only if, |v| is an element of the |vertices| array). +-- +-- Do not use |pairs(g.vertices)| because this may cause your graph +-- drawing algorithm to produce different outputs on different runs. +-- +-- A slightly annoying effect of vertices being able to belong to +-- several graphs at the same time is that the set of arcs incident to +-- a vertex is not a property of the vertex, but rather of the +-- graph. In other words, to get a list of all arcs whose tail is a +-- given vertex |v|, you cannot say something like |v.outgoings| or +-- perhaps |v:getOutgoings()|. Rather, you have to say |g:outgoing(v)| +-- to get this list: +-- % +--\begin{codeexample}[code only, tikz syntax=false] +--for _,a in ipairs(g:outgoing(v)) do -- g is a Digraph object. +-- pgf.debug ("There is an arc leaving " .. tostring(v) .. +-- " heading to " .. tostring(a.head)) +--end +--\end{codeexample} +-- % +-- Naturally, there is also a method |g:incoming()|. +-- +-- To iterate over all arcs of a graph you can say: +-- % +--\begin{codeexample}[code only, tikz syntax=false] +--for _,v in ipairs(g.vertices) do +-- for _,a in ipairs(g:outgoing(v)) do +-- ... +-- end +--end +--\end{codeexample} +-- +-- However, it will often be more convenient and, in case the there +-- are far less arcs than vertices, also faster to write +-- +--\begin{codeexample}[code only, tikz syntax=false] +--for _,a in ipairs(g.arcs) do +-- ... +--end +--\end{codeexample} +-- +-- @field arcs For any two vertices |t| and |h| of a graph, there may +-- or may not be +-- an arc from |t| to |h|. If this is the case, there is an |Arc| +-- object that represents this arc. Note that, since |Digraph|s are +-- always simple graphs, there can be at most one such object for every +-- pair of vertices. However, you can store any information you like for +-- an |Arc| through a |Storage|, see the |Storage| class for +-- details. Each |Arc| for an edge of the syntactic digraph stores +-- an array called |syntactic_edges| of all the multiple edges that +-- are present in the user's input. +-- +-- Unlike vertices, the arc objects of a graph are always local to a +-- graph; an |Arc| object can never be part of two digraphs at the same +-- time. For this reason, while for vertices it makes sense to create +-- |Vertex| objects independently of any |Digraph| objects, it is not +-- possible to instantiate an |Arc| directly: only the |Digraph| method +-- |connect| is allowed to create new |Arc| objects and it will return +-- any existing arcs instead of creating new ones, if there is already +-- an arc present between two nodes. +-- +-- The |arcs| field of a digraph contains a |LookupTable| of all arc +-- objects present in the |Digraph|. Although you can access this field +-- normally and use it in |ipairs| to iterate over all arcs of a graph, +-- note that this array is actually ``reconstructed lazily'' whenever +-- an arc is deleted from the graph. What happens is the following: As +-- long as you just add arcs to a graph, the |arcs| array gets updated +-- normally. However, when you remove an arc from a graph, the arc does +-- not get removed from the |arcs| array (which would be an expensive +-- operation). Instead, the |arcs| array is invalidated (internally set +-- to |nil|), allowing us to perform a |disconnect| in time +-- $O(1)$. The |arcs| array is then ignored until the next time it is +-- accessed, for instance when a user says |ipairs(g.arcs)|. At this +-- point, the |arcs| array is reconstructed by adding all arcs of all +-- nodes to it. +-- +-- The bottom line of the behavior of the |arcs| field is that (a) the +-- ordering of the elements may change abruptly whenever you remove an +-- arc from a graph and (b) performing $k$ |disconnect| operations in +-- sequence takes time $O(k)$, provided you do not access the |arcs| +-- field between calls. +-- +-- @field syntactic_digraph is a reference to the syntactic digraph +-- from which this graph stems ultimately. This may be a cyclic +-- reference to the graph itself. +-- @field options If present, it will be a table storing +-- the options set for the syntactic digraph. +-- +local Digraph = {} + +local function recalc_arcs (digraph) + local arcs = {} + local vertices = digraph.vertices + for i=1,#vertices do + local out = vertices[i].outgoings[digraph] + for j=1,#out do + arcs[#arcs + 1] = out[j] + end + end + digraph.arcs = arcs + return arcs +end + +Digraph.__index = + function (t, k) + if k == "arcs" then + return recalc_arcs(t) + else + return rawget(Digraph,k) + end + end + + + +-- Namespace +require("pgf.gd.model").Digraph = Digraph + +-- Imports +local Arc = require "pgf.gd.model.Arc" +local LookupTable = require "pgf.gd.lib.LookupTable" +local Vertex = require "pgf.gd.model.Vertex" + + + + + +--- +-- Graphs are created using the |new| method, which takes a table of +-- |initial| values as input (like most |new| methods in the graph +-- drawing system). It is permissible that this table of initial values +-- has a |vertices| field, in which case this array will be copied. In +-- contrast, an |arcs| field in the table will be ignored -- newly +-- created graphs always have an empty arcs set. This means that +-- writing |Digraph.new(g)| where |g| is a graph creates a new graph +-- whose vertex set is the same as |g|'s, but where there are no edges: +-- % +--\begin{codeexample}[code only, tikz syntax=false] +--local g = Digraph.new {} +--g:add { v1, v2, v3 } +--g:connect (v1, v2) +-- +--local h = Digraph.new (g) +--assert (h:contains(v1)) +--assert (not h:arc(v1, v2)) +--\end{codeexample} +-- +-- To completely copy a graph, including all arcs, you have to write: +--\begin{codeexample}[code only, tikz syntax=false] +--local h = Digraph.new (g) +--for _,a in ipairs(g.arcs) do h:connect(a.tail, a.head) end +--\end{codeexample} +-- +-- This operation takes time $O(1)$. +-- +-- @param initial A table of initial values. It is permissible that +-- this array contains a |vertices| field. In this +-- case, this field must be an array and its entries +-- must be nodes, which will be inserted. If initial +-- has an |arcs| field, this field will be ignored. +-- The table must contain a field |syntactic_digraph|, +-- which should normally be the syntactic digraph of +-- the graph, but may also be the string |"self"|, in +-- which case it will be set to the newly created +-- (syntactic) digraph. +-- @return A newly-allocated digraph. +-- +function Digraph.new(initial) + local digraph = {} + setmetatable(digraph, Digraph) + + if initial then + for k,v in pairs(initial or {}) do + digraph [k] = v + end + end + + local vertices = digraph.vertices + digraph.vertices = {} + digraph.arcs = {} + + if vertices then + digraph:add(vertices) + end + return digraph +end + + +--- Add vertices to a digraph. +-- +-- This operation takes time $O(|\verb!array!|)$. +-- +-- @param array An array of to-be-added vertices. +-- +function Digraph:add(array) + local vertices = self.vertices + for i=1,#array do + local v = array[i] + if not vertices[v] then + vertices[v] = true + vertices[#vertices + 1] = v + v.incomings[self] = {} + v.outgoings[self] = {} + end + end +end + + +--- Remove vertices from a digraph. +-- +-- This operation removes an array of vertices from a graph. The +-- operation takes time linear in the number of vertices, regardless of +-- how many vertices are to be removed. Thus, it will be (much) faster +-- to delete many vertices by first compiling them in an array and to +-- then delete them using one call to this method. +-- +-- This operation takes time $O(\max\{|\verb!array!|, |\verb!self.vertices!|\})$. +-- +-- @param array The to-be-removed vertices. +-- +function Digraph:remove(array) + local vertices = self.vertices + + -- Mark all to-be-deleted nodes + for i=1,#array do + local v = array[i] + assert(vertices[v], "to-be-deleted node is not in graph") + vertices[v] = false + end + + -- Disconnect them + for i=1,#array do + self:disconnect(array[i]) + end + + LookupTable.remove(self.vertices, array) +end + + + +--- Test, whether a graph contains a given vertex. +-- +-- This operation takes time $O(1)$. +-- +-- @param v The vertex to be tested. +-- +function Digraph:contains(v) + return v and self.vertices[v] == true +end + + + + +--- +-- Returns the arc between two nodes, provided it exists. Otherwise, +-- |nil| is returned. +-- +-- This operation takes time $O(1)$. +-- +-- @param tail The tail vertex +-- @param head The head vertex +-- +-- @return The arc object connecting them +-- +function Digraph:arc(tail, head) + local out = tail.outgoings[self] + if out then + return out[head] + end +end + + + +--- +-- Returns an array containing the outgoing arcs of a vertex. You may +-- only iterate over his array using ipairs, not using pairs. +-- +-- This operation takes time $O(1)$. +-- +-- @param v The vertex +-- +-- @return An array of all outgoing arcs of this vertex (all arcs +-- whose tail is the vertex) +-- +function Digraph:outgoing(v) + return assert(v.outgoings[self], "vertex not in graph") +end + + + +--- +-- Sorts the array of outgoing arcs of a vertex. This allows you to +-- later iterate over the outgoing arcs in a specific order. +-- +-- This operation takes time $O(|\verb!outgoing!| \log |\verb!outgoings!|)$. +-- +-- @param v The vertex +-- @param f A comparison function that is passed to |table.sort| +-- +function Digraph:sortOutgoing(v, f) + table.sort(assert(v.outgoings[self], "vertex not in graph"), f) +end + + +--- +-- Reorders the array of outgoing arcs of a vertex. The parameter array +-- \emph{must} contain the same set of vertices as the outgoing array, +-- but possibly in a different order. +-- +-- This operation takes time $O(|\verb!outgoing!|)$, where |outgoing| +-- is the array of |v|'s outgoing arcs in |self|. +-- +-- @param v The vertex +-- @param vertices An array containing the outgoing vertices in some order. +-- +function Digraph:orderOutgoing(v, vertices) + local outgoing = assert (v.outgoings[self], "vertex not in graph") + assert (#outgoing == #vertices) + + -- Create back hash + local lookup = {} + for i=1,#vertices do + lookup[vertices[i]] = i + end + + -- Compute ordering of the arcs + local reordered = {} + for _,arc in ipairs(outgoing) do + reordered [lookup[arc.head]] = arc + end + + -- Copy back + for i=1,#outgoing do + outgoing[i] = assert(reordered[i], "illegal vertex order") + end +end + + + +--- See |outgoing|. +-- +function Digraph:incoming(v) + return assert(v.incomings[self], "vertex not in graph") +end + + +--- +-- See |sortOutgoing|. +-- +function Digraph:sortIncoming(v, f) + table.sort(assert(v.incomings[self], "vertex not in graph"), f) +end + + +--- +-- See |orderOutgoing|. +-- +function Digraph:orderIncoming(v, vertices) + local incoming = assert (v.incomings[self], "vertex not in graph") + assert (#incoming == #vertices) + + -- Create back hash + local lookup = {} + for i=1,#vertices do + lookup[vertices[i]] = i + end + + -- Compute ordering of the arcs + local reordered = {} + for _,arc in ipairs(incoming) do + reordered [lookup[arc.head]] = arc + end + + -- Copy back + for i=1,#incoming do + incoming[i] = assert(reordered[i], "illegal vertex order") + end +end + + + + + +--- +-- Connects two nodes by an arc and returns the newly created arc +-- object. If they are already connected, the existing arc is returned. +-- +-- This operation takes time $O(1)$. +-- +-- @param s The tail vertex +-- @param t The head vertex (may be identical to |tail| in case of a +-- loop) +-- +-- @return The arc object connecting them (either newly created or +-- already existing) +-- +function Digraph:connect(s, t) + assert (s and t and self.vertices[s] and self.vertices[t], "trying connect nodes not in graph") + + local s_outgoings = s.outgoings[self] + local arc = s_outgoings[t] + + if not arc then + -- Ok, create and insert new arc object + arc = { + tail = s, + head = t, + option_cache = {}, + syntactic_digraph = self.syntactic_digraph, + syntactic_edges = {} + } + setmetatable(arc, Arc) + + -- Insert into outgoings: + s_outgoings [#s_outgoings + 1] = arc + s_outgoings [t] = arc + + local t_incomings = t.incomings[self] + -- Insert into incomings: + t_incomings [#t_incomings + 1] = arc + t_incomings [s] = arc + + -- Insert into arcs field, if it exists: + local arcs = rawget(self, "arcs") + if arcs then + arcs[#arcs + 1] = arc + end + end + + return arc +end + + + + +--- +-- Disconnect either a single vertex |v| from all its neighbors (remove all +-- incoming and outgoing arcs of this vertex) or, in case two nodes +-- are given as parameter, remove the arc between them, if it exists. +-- +-- This operation takes time $O(|I_v| + |I_t|)$, where $I_x$ is the set +-- of vertices incident to $x$, to remove the single arc between $v$ and +-- $v$. For a single vertex $v$, it takes time $O(\sum_{y: \text{there is some +-- arc between $v$ and $y$ or $y$ and $v$}} |I_y|)$. +-- +-- @param v The single vertex or the tail vertex +-- @param t The head vertex +-- +function Digraph:disconnect(v, t) + if t then + -- Case 2: Remove a single arc. + local s_outgoings = assert(v.outgoings[self], "tail node not in graph") + local t_incomings = assert(t.incomings[self], "head node not in graph") + + if s_outgoings[t] then + -- Remove: + s_outgoings[t] = nil + for i=1,#s_outgoings do + if s_outgoings[i].head == t then + table.remove (s_outgoings, i) + break + end + end + t_incomings[v] = nil + for i=1,#t_incomings do + if t_incomings[i].tail == v then + table.remove (t_incomings, i) + break + end + end + self.arcs = nil -- invalidate arcs field + end + else + -- Case 1: Remove all arcs incident to v: + + -- Step 1: Delete all incomings arcs: + local incomings = assert(v.incomings[self], "node not in graph") + local vertices = self.vertices + + for i=1,#incomings do + local s = incomings[i].tail + if s ~= v and vertices[s] then -- skip self-loop and to-be-deleted nodes + -- Remove this arc from s: + local s_outgoings = s.outgoings[self] + s_outgoings[v] = nil + for i=1,#s_outgoings do + if s_outgoings[i].head == v then + table.remove (s_outgoings, i) + break + end + end + end + end + + -- Step 2: Delete all outgoings arcs: + local outgoings = v.outgoings[self] + for i=1,#outgoings do + local t = outgoings[i].head + if t ~= v and vertices[t] then + local t_incomings = t.incomings[self] + t_incomings[v] = nil + for i=1,#t_incomings do + if t_incomings[i].tail == v then + table.remove (t_incomings, i) + break + end + end + end + end + + if #incomings > 0 or #outgoings > 0 then + self.arcs = nil -- invalidate arcs field + end + + -- Step 3: Reset incomings and outgoings fields + v.incomings[self] = {} + v.outgoings[self] = {} + end +end + + + + +--- +-- An arc is changed so that instead of connecting |self.tail| +-- and |self.head|, it now connects a new |head| and |tail|. The +-- difference to first disconnecting and then reconnecting is that all +-- fields of the arc (other than |head| and |tail|, of course), will +-- be ``moved along''. Reconnecting an arc in the same way as before has no +-- effect. +-- +-- If there is already an arc at the new position, fields of the +-- to-be-reconnected arc overwrite fields of the original arc. This is +-- especially dangerous with a syntactic digraph, so do not reconnect +-- arcs of the syntactic digraph (which you should not do anyway). +-- +-- The |arc| object may no longer be valid after a reconnect, but the +-- operation returns the new arc object. +-- +-- This operation needs the time of a disconnect (if necessary). +-- +-- @param arc The original arc object +-- @param tail The new tail vertex +-- @param head The new head vertex +-- +-- @return The new arc object connecting them (either newly created or +-- already existing) +-- +function Digraph:reconnect(arc, tail, head) + assert (arc and tail and head, "connect with nil parameters") + + if arc.head == head and arc.tail == tail then + -- Nothing to be done + return arc + else + local new_arc = self:connect(tail, head) + + for k,v in pairs(arc) do + if k ~= "head" and k ~= "tail" then + new_arc[k] = v + end + end + + -- Remove old arc: + self:disconnect(arc.tail, arc.head) + + return new_arc + end +end + + + +--- +-- Collapse a set of vertices into a single vertex +-- +-- Often, algorithms will wish to treat a whole set of vertices ``as a +-- single vertex''. The idea is that a new vertex is then inserted +-- into the graph, and this vertex is connected to all vertices to +-- which any of the original vertices used to be connected. +-- +-- The |collapse| method takes an array of to-be-collapsed vertices as +-- well as a vertex. First, it will store references to the +-- to-be-collapsed vertices inside the vertex. Second, we iterate over +-- all arcs of the to-be-collapsed vertices. If this arc connects a +-- to-be-collapsed vertex with a not-to-be-collapsed vertex, the +-- not-to-be-collapsed vertex is connected to the collapse +-- vertex. Additionally, the arc is stored at the vertex. +-- +-- Note that the collapse vertex will be added to the graph if it is +-- not already an element. The collapsed vertices will not be removed +-- from the graph, so you must remove them yourself, if necessary. +-- +-- A collapse vertex will store the collapsed vertices so that you can +-- call |expand| later on to ``restore'' the vertices and arcs that +-- were saved during a collapse. This storage is \emph{not} local to +-- the graph in which the collapse occurred. +-- +-- @param collapse_vertices An array of to-be-collapsed vertices +-- @param collapse_vertex The vertex that represents the collapse. If +-- missing, a vertex will be created automatically and added to the graph. +-- @param vertex_fun This function is called for each to-be-collapsed +-- vertex. The parameters are the collapse vertex and the +-- to-be-collapsed vertex. May be |nil|. +-- @param arc_fun This function is called whenever a new arc is added +-- between |rep| and some other vertex. The arguments are the new arc +-- and the original arc. May be |nil|. +-- +-- @return The new vertex that represents the collapsed vertices. + +function Digraph:collapse(collapse_vertices, collapse_vertex, vertex_fun, arc_fun) + + + -- Create and add node, if necessary. + if not collapse_vertex then + collapse_vertex = Vertex.new {} + end + self:add {collapse_vertex} + + -- Copy the collapse_vertices and create lookup + local cvs = {} + for i=1,#collapse_vertices do + local v = collapse_vertices[i] + cvs[i] = v + cvs[v] = true + end + assert (cvs[collapse_vertex] ~= true, "collapse_vertex is in collapse_vertices") + + -- Connected collapse_vertex appropriately + local collapsed_arcs = {} + + if not arc_fun then + arc_fun = function () end + end + + for _,v in ipairs(cvs) do + if vertex_fun then + vertex_fun (collapse_vertex, v) + end + for _,a in ipairs(v.outgoings[self]) do + if cvs[a.head] ~= true then + arc_fun (self:connect(collapse_vertex, a.head), a) + collapsed_arcs[#collapsed_arcs + 1] = a + end + end + for _,a in ipairs(v.incomings[self]) do + if cvs[a.tail] ~= true then + arc_fun (self:connect(a.tail, collapse_vertex), a) + end + collapsed_arcs[#collapsed_arcs + 1] = a + end + end + + -- Remember the old vertices. + collapse_vertex.collapsed_vertices = cvs + collapse_vertex.collapsed_arcs = collapsed_arcs + + return collapse_vertex +end + + + +--- +-- Expand a previously collapsed vertex. +-- +-- If you have collapsed a set of vertices in a graph using +-- |collapse|, you can expand this set once more using this method. It +-- will add all vertices that were previously removed from the graph +-- and will also reinstall the deleted arcs. The collapse vertex is +-- not removed. +-- +-- @param vertex A to-be-expanded vertex that was previously returned +-- by |collapse|. +-- @param vertex_fun A function that is called once for each +-- reinserted vertex. The parameters are the collapse vertex and the +-- reinstalled vertex. May be |nil|. +-- @param arc_fun A function that is called once for each +-- reinserted arc. The parameter is the arc and the |vertex|. May be |nil|. +-- +function Digraph:expand(vertex, vertex_fun, arc_fun) + local cvs = assert(vertex.collapsed_vertices, "no expand information stored") + + -- Add all vertices: + self:add(cvs) + if vertex_fun then + for _,v in ipairs(cvs) do + vertex_fun(vertex, v) + end + end + + -- Add all arcs: + for _,arc in ipairs(vertex.collapsed_arcs) do + local new_arc = self:connect(arc.tail, arc.head) + + for k,v in pairs(arc) do + if k ~= "head" and k ~= "tail" then + new_arc[k] = v + end + end + + if arc_fun then + arc_fun(new_arc, vertex) + end + end +end + + + + + +--- +-- Invokes the |sync| method for all arcs of the graph. +-- +-- @see Arc:sync() +-- +function Digraph:sync() + for _,a in ipairs(self.arcs) do + a:sync() + end +end + + + +--- +-- Computes a string representation of this graph including all nodes +-- and edges. The syntax of this representation is such that it can be +-- used directly in \tikzname's |graph| syntax. +-- +-- @return |self| as string. +-- +function Digraph:__tostring() + local vstrings = {} + local astrings = {} + for i,v in ipairs(self.vertices) do + vstrings[i] = " " .. tostring(v) .. "[x=" .. math.floor(v.pos.x) .. "pt,y=" .. math.floor(v.pos.y) .. "pt]" + local out_arcs = v.outgoings[self] + if #out_arcs > 0 then + local t = {} + for j,a in ipairs(out_arcs) do + t[j] = tostring(a.head) + end + astrings[#astrings + 1] = " " .. tostring(v) .. " -> { " .. table.concat(t,", ") .. " }" + end + end + return "graph [id=" .. tostring(self.vertices) .. "] {\n {\n" .. + table.concat(vstrings, ",\n") .. "\n }; \n" .. + table.concat(astrings, ";\n") .. "\n}"; +end + + + + +-- Done + +return Digraph diff --git a/graphics/pgf/base/tex/generic/graphdrawing/lua/pgf/gd/model/Edge.lua b/graphics/pgf/base/tex/generic/graphdrawing/lua/pgf/gd/model/Edge.lua new file mode 100644 index 0000000000..45cbc60f9f --- /dev/null +++ b/graphics/pgf/base/tex/generic/graphdrawing/lua/pgf/gd/model/Edge.lua @@ -0,0 +1,209 @@ +-- Copyright 2012 by Till Tantau +-- +-- This file may be distributed an/or modified +-- +-- 1. under the LaTeX Project Public License and/or +-- 2. under the GNU Public License +-- +-- See the file doc/generic/pgf/licenses/LICENSE for more information + +-- @release $Header$ + + +--- +-- An |Edge| is a ``syntactic'' connection between two +-- vertices that represents a connection present in the syntactic +-- digraph. Unlike an |Arc|, |Edge| objects are not controlled by the +-- |Digraph| class. Also unlike |Arc| objects, there can be several +-- edges between the same vertices, namely whenever several such edges +-- are present in the syntactic digraph. +-- +-- In detail, the relationship between arcs and edges is as follows: +-- If there is an |Edge| between two vertices $u$ and $v$ in the +-- syntactic digraph, there will be an |Arc| from $u$ to $v$ and the +-- array |syntactic_edges| of this |Arc| object will contain the +-- |Edge| object. In particular, if there are several edges between +-- the same vertices, all of these edges will be part of the array in +-- a single |Arc| object. +-- +-- Edges, like arcs, are always directed from a |tail| vertex to a +-- |head| vertex; this is true even for undirected vertices. The +-- |tail| vertex will always be the vertex that came first in the +-- syntactic specification of the edge, the |head| vertex is the +-- second one. Whether +-- an edge is directed or not depends on the |direction| of the edge, which +-- may be one of the following: +-- % +-- \begin{enumerate} +-- \item |"->"| +-- \item |"--"| +-- \item |"<-"| +-- \item |"<->"| +-- \item |"-!-"| +-- \end{enumerate} +-- +-- +-- @field head The head vertex of this edge. +-- +-- @field tail The tail vertex of this edge. +-- +-- @field event The creation |Event| of this edge. +-- +-- @field options A table of options that contains user-defined options. +-- +-- @field direction One of the directions named above. +-- +-- @field path A |Path| object that describes the path of the +-- edge. The path's coordinates are interpreted \emph{absolutely}. +-- +-- @field generated_options This is an options array that is generated +-- by the algorithm. When the edge is rendered later on, this array +-- will be passed back to the display layer. The syntax is the same as +-- for the |declare_parameter_sequence| function, see +-- |InterfaceToAlgorithms|. +-- +-- @field animations An array of animations, see the |animations| +-- field of the |Vertex| class for the syntax. + +local Edge = {} +Edge.__index = Edge + + +-- Namespace + +require("pgf.gd.model").Edge = Edge + + +-- Imports + +local Path = require "pgf.gd.model.Path" + + +--- +-- Create a new edge. The |initial| parameter allows you to setup +-- some initial values. +-- +-- @usage +--\begin{codeexample}[code only, tikz syntax=false] +--local v = Edge.new { tail = v1, head = v2 } +--\end{codeexample} +-- +-- @param initial Values to override defaults. -- +-- @return A new edge object. +-- +function Edge.new(values) + local new = {} + for k,v in pairs(values) do + new[k] = v + end + new.generated_options = new.generated_options or {} + new.animations = new.animations or {} + if not new.path then + local p = Path.new () + p:appendMoveto(Edge.tailAnchorForEdgePath(new)) + p:appendLineto(Edge.headAnchorForEdgePath(new)) + new.path = p + end + + return setmetatable(new, Edge) +end + + + + +--- +-- This method returns a ``coordinate factory'' that can be used as +-- the coordinate of a |moveto| at the beginning of a path starting at +-- the |tail| of the arc. Suppose you want to create a path starting +-- at the tail vertex, going to the coordinate $(10,10)$ and ending at +-- the head vertex. The trouble is that when you create the path +-- corresponding to this route, you typically do not know where the +-- tail vertex is going to be. In this case, you use this +-- method to get a function that will, later on, compute the correct +-- position of the anchor as needed. +-- +-- Note that you typically do not use this function, but use the +-- corresponding function of the |Arc| class. Use this function only +-- if there are multiple edges between two vertices that need to be +-- routed differently. +-- +-- Here is the code you would use to create the above-mentioned path: +-- % +--\begin{codeexample}[code only, tikz syntax=false] +--local a = g:connect(tail,head) +--local e = a.syntactic_edges[1] +--... +--e.path = Path.new() +--e.path:appendMoveto(e:tailAnchorForEdgePath()) +--e.path:appendLineto(10, 10) +--e.path:appendLineto(e:headAnchorForEdgePath()) +--\end{codeexample} +-- +-- As for the |Arc| class, you can also setup a polyline more easily: +-- % +--\begin{codeexample}[code only, tikz syntax=false] +--e:setPolylinePath { Coordinate.new (10, 10) } +--\end{codeexample} + +function Edge:tailAnchorForEdgePath() + return function () + local a = self.options['tail anchor'] + if a == "" then + a = "center" + end + return self.tail:anchor(a) + self.tail.pos + end +end + +--- +-- See |Arc:tailAnchorForArcPath|. + +function Edge:headAnchorForEdgePath() + return function () + local a = self.options['head anchor'] + if a == "" then + a = "center" + end + return self.head:anchor(a) + self.head.pos + end +end + + + +--- +-- Setup the |path| field of an edge in such a way that it corresponds +-- to a sequence of straight line segments starting at the tail's +-- anchor and ending at the head's anchor. +-- +-- @param coordinates An array of |Coordinates| through which the line +-- will go through. + +function Edge:setPolylinePath(coordinates) + local p = Path.new () + + p:appendMoveto(self:tailAnchorForEdgePath()) + + for _,c in ipairs(coordinates) do + p:appendLineto(c) + end + + p:appendLineto(self:headAnchorForEdgePath()) + + self.path = p +end + + + +-- +-- Returns a string representation of an edge. This is mainly for debugging. +-- +-- @return The Edge as a string. +-- +function Edge:__tostring() + return tostring(self.tail) .. self.direction .. tostring(self.head) +end + + +-- Done + +return Edge diff --git a/graphics/pgf/base/tex/generic/graphdrawing/lua/pgf/gd/model/Hyperedge.lua b/graphics/pgf/base/tex/generic/graphdrawing/lua/pgf/gd/model/Hyperedge.lua new file mode 100644 index 0000000000..cc265023fa --- /dev/null +++ b/graphics/pgf/base/tex/generic/graphdrawing/lua/pgf/gd/model/Hyperedge.lua @@ -0,0 +1,56 @@ +-- Copyright 2012 by Till Tantau +-- +-- This file may be distributed an/or modified +-- +-- 1. under the LaTeX Project Public License and/or +-- 2. under the GNU Public License +-- +-- See the file doc/generic/pgf/licenses/LICENSE for more information + +-- @release $Header$ + + + +--- +-- @section subsection {Hyperedges} +-- +-- @end + + +-- Includes + +local declare = require "pgf.gd.interface.InterfaceToAlgorithms".declare + + +--- + +declare { + key = "hyper", + layer = -10, + + summary = [[" + A \emph{hyperedge} of a graph does not connect just two nodes, but + is any subset of the node set (although a normal edge is also a + hyperedge that happens to contain just two nodes). Internally, a + collection of kind |hyper| is created. Currently, there is + no default renderer for hyper edges. + "]], + documentation = [[" +\begin{codeexample}[code only] +\graph { + % The nodes: + a, b, c, d; + + % The edges: + {[hyper] a,b,c}; + {[hyper] b,c,d}; + {[hyper] a,c}; + {[hyper] d} +}; +\end{codeexample} + "]] +} + +-- Done + +return Hyperedge
\ No newline at end of file diff --git a/graphics/pgf/base/tex/generic/graphdrawing/lua/pgf/gd/model/Path.lua b/graphics/pgf/base/tex/generic/graphdrawing/lua/pgf/gd/model/Path.lua new file mode 100644 index 0000000000..cbd0f079bf --- /dev/null +++ b/graphics/pgf/base/tex/generic/graphdrawing/lua/pgf/gd/model/Path.lua @@ -0,0 +1,1278 @@ +-- Copyright 2014 by Till Tantau +-- +-- This file may be distributed an/or modified +-- +-- 1. under the LaTeX Project Public License and/or +-- 2. under the GNU Public License +-- +-- See the file doc/generic/pgf/licenses/LICENSE for more information + +-- @release $Header$ + + +--- +-- A Path models a path in the plane. +-- +-- Following the PostScript/\textsc{pdf}/\textsc{svg} convention, a +-- path consists of a series of path segments, each of which can be +-- closed or not. Each path segment, in turn, consists of a series of +-- Bézier curves and straight line segments; see +-- Section~\ref{section-paths} for an introduction to paths in +-- general. +-- +-- A |Path| object is a table whose array part stores +-- |Coordinate| objects, |strings|, and |function|s that +-- describe the path of the edge. The following strings are allowed in +-- this array: +-- % +-- \begin{itemize} +-- \item |"moveto"| The line's path should stop at the current +-- position and then start anew at the next coordinate in the array. +-- \item |"lineto"| The line should continue from the current position +-- to the next coordinate in the array. +-- \item |"curveto"| The line should continue form the current +-- position with a Bézier curve that is specified by the next three +-- |Coordinate| objects (in the usual manner). +-- \item |"closepath"| The line's path should be ``closed'' in the sense +-- that the current subpath that was started with the most recent +-- moveto operation should now form a closed curve. +-- \end{itemize} +-- +-- Instead of a |Coordinate|, a |Path| may also contain a function. In +-- this case, the function, when called, must return the |Coordinate| +-- that is ``meant'' by the position. This allows algorithms to +-- add coordinates to a path that are still not fixed at the moment +-- they are added to the path. + +local Path = {} +Path.__index = Path + + +-- Namespace + +require("pgf.gd.model").Path = Path + + +-- Imports + +local Coordinate = require "pgf.gd.model.Coordinate" +local Bezier = require "pgf.gd.lib.Bezier" + +local lib = require "pgf.gd.lib" + + +-- Private function + +function Path.rigid (x) + if type(x) == "function" then + return x() + else + return x + end +end + +local rigid = Path.rigid + + +--- +-- Creates an empty path. +-- +-- @param initial A table containing an array of strings and +-- coordinates that constitute the path. Coordinates may be given as +-- tables or as a pair of numbers. In this case, each pair of numbers +-- is converted into one coordinate. If omitted, a new empty path +-- is created. +-- +-- @return A empty Path +-- +function Path.new(initial) + if initial then + local new = {} + local i = 1 + local count = 0 + while i <= #initial do + local e = initial[i] + if type(e) == "string" then + assert (count == 0, "illformed path") + if e == "moveto" then + count = 1 + elseif e == "lineto" then + count = 1 + elseif e == "closepath" then + count = 0 + elseif e == "curveto" then + count = 3 + else + error ("unknown path command " .. e) + end + new[#new+1] = e + elseif type(e) == "number" then + if count == 0 then + new[#new+1] = "lineto" + else + count = count - 1 + end + new[#new+1] = Coordinate.new(e,initial[i+1]) + i = i + 1 + elseif type(e) == "table" or type(e) == "function" then + if count == 0 then + new[#new+1] = "lineto" + else + count = count - 1 + end + new[#new+1] = e + else + error ("invalid object on path") + end + i = i + 1 + end + return setmetatable(new, Path) + else + return setmetatable({}, Path) + end +end + + +--- +-- Creates a copy of a path. +-- +-- @return A copy of the path + +function Path:clone() + local new = {} + for _,x in ipairs(self) do + if type(x) == "table" then + new[#new+1] = x:clone() + else + new[#new+1] = x + end + end + return setmetatable(new, Path) +end + + + +--- +-- Returns the path in reverse order. +-- +-- @return A copy of the reversed path + +function Path:reversed() + + -- First, build segments + local subpaths = {} + local subpath = {} + + local function closepath () + if subpath.start then + subpaths [#subpaths + 1] = subpath + subpath = {} + end + end + + local prev + local start + + local i = 1 + while i <= #self do + local x = self[i] + if x == "lineto" then + subpath[#subpath+1] = { + action = 'lineto', + from = prev, + to = self[i+1] + } + prev = self[i+1] + i = i + 2 + elseif x == "moveto" then + closepath() + prev = self[i+1] + start = prev + subpath.start = prev + i = i + 2 + elseif x == "closepath" then + subpath [#subpath + 1] = { + action = "closepath", + from = prev, + to = start, + } + prev = nil + start = nil + closepath() + i = i + 1 + elseif x == "curveto" then + local s1, s2, to = self[i+1], self[i+2], self[i+3] + subpath [#subpath + 1] = { + action = "curveto", + from = prev, + to = to, + support_1 = s1, + support_2 = s2, + } + prev = self[i+3] + i = i + 4 + else + error ("illegal path command '" .. x .. "'") + end + end + closepath () + + local new = Path.new () + + for _,subpath in ipairs(subpaths) do + if #subpath == 0 then + -- A subpath that consists only of a moveto: + new:appendMoveto(subpath.start) + else + -- We start with a moveto to the end point: + new:appendMoveto(subpath[#subpath].to) + + -- Now walk backwards: + for i=#subpath,1,-1 do + if subpath[i].action == "lineto" then + new:appendLineto(subpath[i].from) + elseif subpath[i].action == "closepath" then + new:appendLineto(subpath[i].from) + elseif subpath[i].action == "curveto" then + new:appendCurveto(subpath[i].support_2, + subpath[i].support_1, + subpath[i].from) + else + error("illegal path command") + end + end + + -- Append a closepath, if necessary + if subpath[#subpath].action == "closepath" then + new:appendClosepath() + end + end + end + + return new +end + + +--- +-- Transform all points on a path. +-- +-- @param t A transformation, see |pgf.gd.lib.Transform|. It is +-- applied to all |Coordinate| objects on the path. + +function Path:transform(t) + for _,c in ipairs(self) do + if type(c) == "table" then + c:apply(t) + end + end +end + + +--- +-- Shift all points on a path. +-- +-- @param x An $x$-shift +-- @param y A $y$-shift + +function Path:shift(x,y) + for _,c in ipairs(self) do + if type(c) == "table" then + c.x = c.x + x + c.y = c.y + y + end + end +end + + +--- +-- Shift by all points on a path. +-- +-- @param x A coordinate + +function Path:shiftByCoordinate(x) + for _,c in ipairs(self) do + if type(c) == "table" then + c.x = c.x + x.x + c.y = c.y + x.y + end + end +end + + +--- +-- Makes the path empty. +-- + +function Path:clear() + for i=1,#self do + self[i] = nil + end +end + + +--- +-- Appends a |moveto| to the path. +-- +-- @param x A |Coordinate| or |function| or, if the |y| parameter is +-- not |nil|, a number that is the $x$-part of a coordinate. +-- @param y The $y$-part of the coordinate. + +function Path:appendMoveto(x,y) + self[#self + 1] = "moveto" + self[#self + 1] = y and Coordinate.new(x,y) or x +end + + +--- +-- Appends a |lineto| to the path. +-- +-- @param x A |Coordinate| or |function|, if the |y| parameter is not +-- |nil|, a number that is the $x$-part of a coordinate. +-- @param y The $y$-part of the coordinate. + +function Path:appendLineto(x,y) + self[#self + 1] = "lineto" + self[#self + 1] = y and Coordinate.new(x,y) or x +end + + + +--- +-- Appends a |closepath| to the path. + +function Path:appendClosepath() + self[#self + 1] = "closepath" +end + + +--- +-- Appends a |curveto| to the path. There can be either three +-- coordinates (or functions) as parameters (the two support points +-- and the target) or six numbers, where two consecutive numbers form a +-- |Coordinate|. Which case is meant is detected by the presence of a +-- sixth non-nil parameter. + +function Path:appendCurveto(a,b,c,d,e,f) + self[#self + 1] = "curveto" + if f then + self[#self + 1] = Coordinate.new(a,b) + self[#self + 1] = Coordinate.new(c,d) + self[#self + 1] = Coordinate.new(e,f) + else + self[#self + 1] = a + self[#self + 1] = b + self[#self + 1] = c + end +end + + + + + + +--- +-- Makes a path ``rigid'', meaning that all coordinates that are only +-- given as functions are replaced by the values these functions +-- yield. + +function Path:makeRigid() + for i=1,#self do + self[i] = rigid(self[i]) + end +end + + +--- +-- Returns an array of all coordinates that are present in a +-- path. This means, essentially, that all strings are filtered out. +-- +-- @return An array of all coordinate objects on the path. + +function Path:coordinates() + local cloud = {} + for i=1,#self do + local p = self[i] + if type(p) == "table" then + cloud[#cloud + 1] = p + elseif type(p) == "function" then + cloud[#cloud + 1] = p() + end + end + return cloud +end + + +--- +-- Returns a bounding box of the path. This will not necessarily be +-- the minimal bounding box in case the path contains curves because, +-- then, the support points of the curve are used for the computation +-- rather than the actual bounding box of the path. +-- +-- If the path contains no coordinates, all return values are 0. +-- +-- @return |min_x| The minimum $x$ value of the bounding box of the path +-- @return |min_y| The minimum $y$ value +-- @return |max_x| +-- @return |max_y| +-- @return |center_x| The center of the bounding box +-- @return |center_y| + +function Path:boundingBox() + if #self > 0 then + local min_x, min_y = math.huge, math.huge + local max_x, max_y = -math.huge, -math.huge + + for i=1,#self do + local c = rigid(self[i]) + if type(c) == "table" then + local x = c.x + local y = c.y + if x < min_x then min_x = x end + if y < min_y then min_y = y end + if x > max_x then max_x = x end + if y > max_y then max_y = y end + end + end + + if min_x ~= math.huge then + return min_x, min_y, max_x, max_y, (min_x+max_x) / 2, (min_y+max_y) / 2 + end + end + return 0, 0, 0, 0, 0, 0 +end + + +-- Forwards + +local segmentize, bb, boxes_intersect, intersect_curves + +local eps = 0.0001 + + + +--- +-- Computes all intersections of a path with another path and returns +-- them as an array of coordinates. The intersections will be sorted +-- ``along the path |self|''. The implementation uses a +-- divide-and-conquer approach that should be reasonably fast in +-- practice. +-- +-- @param path Another path +-- +-- @return Array of all intersections of |path| with |self| in the +-- order they appear on |self|. Each entry of this array is a table +-- with the following fields: +-- % +-- \begin{itemize} +-- \item |index| The index of the segment in |self| where +-- the intersection occurs. +-- \item |time| The ``time'' at which a point traveling along the +-- segment from its start point to its end point. +-- \item |point| The point itself. +-- \end{itemize} + +function Path:intersectionsWith(path) + + local p1 = segmentize(self) + local memo1 = prepare_memo(p1) + local p2 = segmentize(path) + local memo2 = prepare_memo(p2) + + local intersections = {} + + local function intersect_segments(i1, i2) + + local s1 = p1[i1] + local s2 = p2[i2] + local r = {} + + if s1.action == 'lineto' and s2.action == 'lineto' then + local a = s2.to.x - s2.from.x + local b = s1.from.x - s1.to.x + local c = s2.from.x - s1.from.x + local d = s2.to.y - s2.from.y + local e = s1.from.y - s1.to.y + local f = s2.from.y - s1.from.y + + local det = a*e - b*d + + if math.abs(det) > eps*eps then + local t, s = (c*d - a*f)/det, (b*f - e*c)/det + + if t >= 0 and t<=1 and s>=0 and s <= 1 then + local p = s1.from:clone() + p:moveTowards(s1.to, t) + return { { time = t, point = p } } + end + end + elseif s1.action == 'lineto' and s2.action == 'curveto' then + intersect_curves (0, 1, + s1.from.x, s1.from.y, + s1.from.x*2/3+s1.to.x*1/3, s1.from.y*2/3+s1.to.y*1/3, + s1.from.x*1/3+s1.to.x*2/3, s1.from.y*1/3+s1.to.y*2/3, + s1.to.x, s1.to.y, + s2.from.x, s2.from.y, + s2.support_1.x, s2.support_1.y, + s2.support_2.x, s2.support_2.y, + s2.to.x, s2.to.y, + r) + elseif s1.action == 'curveto' and s2.action == 'lineto' then + intersect_curves (0, 1, + s1.from.x, s1.from.y, + s1.support_1.x, s1.support_1.y, + s1.support_2.x, s1.support_2.y, + s1.to.x, s1.to.y, + s2.from.x, s2.from.y, + s2.from.x*2/3+s2.to.x*1/3, s2.from.y*2/3+s2.to.y*1/3, + s2.from.x*1/3+s2.to.x*2/3, s2.from.y*1/3+s2.to.y*2/3, + s2.to.x, s2.to.y, + r) + else + intersect_curves (0, 1, + s1.from.x, s1.from.y, + s1.support_1.x, s1.support_1.y, + s1.support_2.x, s1.support_2.y, + s1.to.x, s1.to.y, + s2.from.x, s2.from.y, + s2.support_1.x, s2.support_1.y, + s2.support_2.x, s2.support_2.y, + s2.to.x, s2.to.y, + r) + end + return r + end + + local function intersect (i1, j1, i2, j2) + + if i1 > j1 or i2 > j2 then + return + end + + local bb1 = bb(i1, j1, memo1) + local bb2 = bb(i2, j2, memo2) + + if boxes_intersect(bb1, bb2) then + -- Ok, need to do something + if i1 == j1 and i2 == j2 then + local intersects = intersect_segments (i1, i2) + for _,t in ipairs(intersects) do + intersections[#intersections+1] = { + time = t.time, + index = p1[i1].path_pos, + point = t.point + } + end + elseif i1 == j1 then + local m2 = math.floor((i2 + j2) / 2) + intersect(i1, j1, i2, m2) + intersect(i1, j1, m2+1, j2) + elseif i2 == j2 then + local m1 = math.floor((i1 + j1) / 2) + intersect(i1, m1, i2, j2) + intersect(m1+1, j1, i2, j2) + else + local m1 = math.floor((i1 + j1) / 2) + local m2 = math.floor((i2 + j2) / 2) + intersect(i1, m1, i2, m2) + intersect(m1+1, j1, i2, m2) + intersect(i1, m1, m2+1, j2) + intersect(m1+1, j1, m2+1, j2) + end + end + end + + -- Run the recursion + intersect(1, #p1, 1, #p2) + + -- Sort + table.sort(intersections, function(a,b) + return a.index < b.index or + a.index == b.index and a.time < b.time + end) + + -- Remove duplicates + local remains = {} + remains[1] = intersections[1] + for i=2,#intersections do + local next = intersections[i] + local prev = remains[#remains] + if math.abs(next.point.x - prev.point.x) + math.abs(next.point.y - prev.point.y) > eps then + remains[#remains+1] = next + end + end + + return remains +end + + +-- Returns true if two bounding boxes intersection + +function boxes_intersect (bb1, bb2) + return (bb1.max_x >= bb2.min_x - eps*eps and + bb1.min_x <= bb2.max_x + eps*eps and + bb1.max_y >= bb2.min_y - eps*eps and + bb1.min_y <= bb2.max_y + eps*eps) +end + + +-- Turns a path into a sequence of segments, each being either a +-- lineto or a curveto from some point to another point. It also sets +-- up a memorization array for the bounding boxes. + +function segmentize (path) + + local prev + local start + local s = {} + + local i = 1 + while i <= #path do + local x = path[i] + + if x == "lineto" then + x = rigid(path[i+1]) + s [#s + 1] = { + path_pos = i, + action = "lineto", + from = prev, + to = x, + bb = { + min_x = math.min(prev.x, x.x), + max_x = math.max(prev.x, x.x), + min_y = math.min(prev.y, x.y), + max_y = math.max(prev.y, x.y), + } + } + prev = x + i = i + 2 + elseif x == "moveto" then + prev = rigid(path[i+1]) + start = prev + i = i + 2 + elseif x == "closepath" then + s [#s + 1] = { + path_pos = i, + action = "lineto", + from = prev, + to = start, + bb = { + min_x = math.min(prev.x, start.x), + max_x = math.max(prev.x, start.x), + min_y = math.min(prev.y, start.y), + max_y = math.max(prev.y, start.y), + } + } + prev = nil + start = nil + i = i + 1 + elseif x == "curveto" then + local s1, s2, to = rigid(path[i+1]), rigid(path[i+2]), rigid(path[i+3]) + s [#s + 1] = { + action = "curveto", + path_pos = i, + from = prev, + to = to, + support_1 = s1, + support_2 = s2, + bb = { + min_x = math.min(prev.x, s1.x, s2.x, to.x), + max_x = math.max(prev.x, s1.x, s2.x, to.x), + min_y = math.min(prev.y, s1.y, s2.y, to.y), + max_y = math.max(prev.y, s1.y, s2.y, to.y), + } + } + prev = path[i+3] + i = i + 4 + else + error ("illegal path command '" .. x .. "'") + end + end + + return s +end + + +function prepare_memo (s) + + local memo = {} + + memo.base = #s + + -- Fill memo table + for i,e in ipairs (s) do + memo[i*#s + i] = e.bb + end + + return memo +end + + +-- This function computes the bounding box of all segments between i +-- and j (inclusively) + +function bb (i, j, memo) + local b = memo[memo.base*i + j] + if not b then + assert (i < j, "memorization table filled incorrectly") + + local mid = math.floor((i+j)/2) + local bb1 = bb (i, mid, memo) + local bb2 = bb (mid+1, j, memo) + b = { + min_x = math.min(bb1.min_x, bb2.min_x), + max_x = math.max(bb1.max_x, bb2.max_x), + min_y = math.min(bb1.min_y, bb2.min_y), + max_y = math.max(bb1.max_y, bb2.max_y) + } + memo[memo.base*i + j] = b + end + + return b +end + + + +-- Intersect two Bézier curves. + +function intersect_curves(t0, t1, + c1_ax, c1_ay, c1_bx, c1_by, + c1_cx, c1_cy, c1_dx, c1_dy, + c2_ax, c2_ay, c2_bx, c2_by, + c2_cx, c2_cy, c2_dx, c2_dy, + intersections) + + -- Only do something, if the bounding boxes intersect: + local c1_min_x = math.min(c1_ax, c1_bx, c1_cx, c1_dx) + local c1_max_x = math.max(c1_ax, c1_bx, c1_cx, c1_dx) + local c1_min_y = math.min(c1_ay, c1_by, c1_cy, c1_dy) + local c1_max_y = math.max(c1_ay, c1_by, c1_cy, c1_dy) + local c2_min_x = math.min(c2_ax, c2_bx, c2_cx, c2_dx) + local c2_max_x = math.max(c2_ax, c2_bx, c2_cx, c2_dx) + local c2_min_y = math.min(c2_ay, c2_by, c2_cy, c2_dy) + local c2_max_y = math.max(c2_ay, c2_by, c2_cy, c2_dy) + + if c1_max_x >= c2_min_x and + c1_min_x <= c2_max_x and + c1_max_y >= c2_min_y and + c1_min_y <= c2_max_y then + + -- Everything "near together"? + if c1_max_x - c1_min_x < eps and c1_max_y - c1_min_y < eps then + + -- Compute intersection of lines c1_a to c1_d and c2_a to c2_d + local a = c2_dx - c2_ax + local b = c1_ax - c1_dx + local c = c2_ax - c1_ax + local d = c2_dy - c2_ay + local e = c1_ay - c1_dy + local f = c2_ay - c1_ay + + local det = a*e - b*d + local t + + t = (c*d - a*f)/det + if t<0 then + t=0 + elseif t>1 then + t=1 + end + + intersections [#intersections + 1] = { + time = t0 + t*(t1-t0), + point = Coordinate.new(c1_ax + t*(c1_dx-c1_ax), c1_ay+t*(c1_dy-c1_ay)) + } + else + -- Cut 'em in half! + local c1_ex, c1_ey = (c1_ax + c1_bx)/2, (c1_ay + c1_by)/2 + local c1_fx, c1_fy = (c1_bx + c1_cx)/2, (c1_by + c1_cy)/2 + local c1_gx, c1_gy = (c1_cx + c1_dx)/2, (c1_cy + c1_dy)/2 + + local c1_hx, c1_hy = (c1_ex + c1_fx)/2, (c1_ey + c1_fy)/2 + local c1_ix, c1_iy = (c1_fx + c1_gx)/2, (c1_fy + c1_gy)/2 + + local c1_jx, c1_jy = (c1_hx + c1_ix)/2, (c1_hy + c1_iy)/2 + + local c2_ex, c2_ey = (c2_ax + c2_bx)/2, (c2_ay + c2_by)/2 + local c2_fx, c2_fy = (c2_bx + c2_cx)/2, (c2_by + c2_cy)/2 + local c2_gx, c2_gy = (c2_cx + c2_dx)/2, (c2_cy + c2_dy)/2 + + local c2_hx, c2_hy = (c2_ex + c2_fx)/2, (c2_ey + c2_fy)/2 + local c2_ix, c2_iy = (c2_fx + c2_gx)/2, (c2_fy + c2_gy)/2 + + local c2_jx, c2_jy = (c2_hx + c2_ix)/2, (c2_hy + c2_iy)/2 + + intersect_curves (t0, (t0+t1)/2, + c1_ax, c1_ay, c1_ex, c1_ey, c1_hx, c1_hy, c1_jx, c1_jy, + c2_ax, c2_ay, c2_ex, c2_ey, c2_hx, c2_hy, c2_jx, c2_jy, + intersections) + intersect_curves (t0, (t0+t1)/2, + c1_ax, c1_ay, c1_ex, c1_ey, c1_hx, c1_hy, c1_jx, c1_jy, + c2_jx, c2_jy, c2_ix, c2_iy, c2_gx, c2_gy, c2_dx, c2_dy, + intersections) + intersect_curves ((t0+t1)/2, t1, + c1_jx, c1_jy, c1_ix, c1_iy, c1_gx, c1_gy, c1_dx, c1_dy, + c2_ax, c2_ay, c2_ex, c2_ey, c2_hx, c2_hy, c2_jx, c2_jy, + intersections) + intersect_curves ((t0+t1)/2, t1, + c1_jx, c1_jy, c1_ix, c1_iy, c1_gx, c1_gy, c1_dx, c1_dy, + c2_jx, c2_jy, c2_ix, c2_iy, c2_gx, c2_gy, c2_dx, c2_dy, + intersections) + end + end +end + + +--- +-- Shorten a path at the beginning. We are given the index of a +-- segment inside the path as well as a point in time along this +-- segment. The path is now shortened so that everything before this +-- segment and everything in the segment before the given time is +-- removed from the path. +-- +-- @param index The index of a path segment. +-- @param time A time along the specified path segment. + +function Path:cutAtBeginning(index, time) + + local cut_path = Path:new () + + -- Ok, first, we need to find the segment *before* the current + -- one. Usually, this will be a moveto or a lineto, but things could + -- be different. + assert (type(self[index-1]) == "table" or type(self[index-1]) == "function", + "segment before intersection does not end with a coordinate") + + local from = rigid(self[index-1]) + local action = self[index] + + -- Now, depending on the type of segment, we do different things: + if action == "lineto" then + + -- Ok, compute point: + local to = rigid(self[index+1]) + + from:moveTowards(to, time) + + -- Ok, this is easy: We start with a fresh moveto ... + cut_path[1] = "moveto" + cut_path[2] = from + + -- ... and copy the rest + for i=index,#self do + cut_path[#cut_path+1] = self[i] + end + elseif action == "curveto" then + + local to = rigid(self[index+3]) + local s1 = rigid(self[index+1]) + local s2 = rigid(self[index+2]) + + -- Now, compute the support vectors and the point at time: + from:moveTowards(s1, time) + s1:moveTowards(s2, time) + s2:moveTowards(to, time) + + from:moveTowards(s1, time) + s1:moveTowards(s2, time) + + from:moveTowards(s1, time) + + -- Ok, this is easy: We start with a fresh moveto ... + cut_path[1] = "moveto" + cut_path[2] = from + cut_path[3] = "curveto" + cut_path[4] = s1 + cut_path[5] = s2 + cut_path[6] = to + + -- ... and copy the rest + for i=index+4,#self do + cut_path[#cut_path+1] = self[i] + end + + elseif action == "closepath" then + -- Let us find the start point: + local found + for i=index,1,-1 do + if self[i] == "moveto" then + -- Bingo: + found = i + break + end + end + + assert(found, "no moveto found in path") + + local to = rigid(self[found+1]) + from:moveTowards(to,time) + + cut_path[1] = "moveto" + cut_path[2] = from + cut_path[3] = "lineto" + cut_path[4] = to + + -- ... and copy the rest + for i=index+1,#self do + cut_path[#cut_path+1] = self[i] + end + else + error ("wrong path operation") + end + + -- Move cut_path back: + for i=1,#cut_path do + self[i] = cut_path[i] + end + for i=#cut_path+1,#self do + self[i] = nil + end +end + + + + +--- +-- Shorten a path at the end. This method works like |cutAtBeginning|, +-- only the path is cut at the end. +-- +-- @param index The index of a path segment. +-- @param time A time along the specified path segment. + +function Path:cutAtEnd(index, time) + + local cut_path = Path:new () + + -- Ok, first, we need to find the segment *before* the current + -- one. Usually, this will be a moveto or a lineto, but things could + -- be different. + assert (type(self[index-1]) == "table" or type(self[index-1]) == "function", + "segment before intersection does not end with a coordinate") + + local from = rigid(self[index-1]) + local action = self[index] + + -- Now, depending on the type of segment, we do different things: + if action == "lineto" then + + -- Ok, compute point: + local to = rigid(self[index+1]) + to:moveTowards(from, 1-time) + + for i=1,index do + cut_path[i] = self[i] + end + cut_path[index+1] = to + + elseif action == "curveto" then + + local s1 = rigid(self[index+1]) + local s2 = rigid(self[index+2]) + local to = rigid(self[index+3]) + + -- Now, compute the support vectors and the point at time: + to:moveTowards(s2, 1-time) + s2:moveTowards(s1, 1-time) + s1:moveTowards(from, 1-time) + + to:moveTowards(s2, 1-time) + s2:moveTowards(s1, 1-time) + + to:moveTowards(s2, 1-time) + + -- ... and copy the rest + for i=1,index do + cut_path[i] = self[i] + end + + cut_path[index+1] = s1 + cut_path[index+2] = s2 + cut_path[index+3] = to + + elseif action == "closepath" then + -- Let us find the start point: + local found + for i=index,1,-1 do + if self[i] == "moveto" then + -- Bingo: + found = i + break + end + end + + assert(found, "no moveto found in path") + + local to = rigid(self[found+1]:clone()) + to:moveTowards(from,1-time) + + for i=1,index-1 do + cut_path[i] = self[i] + end + cut_path[index] = 'lineto' + cut_path[index+1] = to + else + error ("wrong path operation") + end + + -- Move cut_path back: + for i=1,#cut_path do + self[i] = cut_path[i] + end + for i=#cut_path+1,#self do + self[i] = nil + end +end + + + + +--- +-- ``Pads'' the path. The idea is the following: Suppose we stroke the +-- path with a pen whose width is twice the value |padding|. The outer +-- edge of this stroked drawing is now a path by itself. The path will +-- be a bit longer and ``larger''. The present function tries to +-- compute an approximation to this resulting path. +-- +-- The algorithm used to compute the enlarged part does not necessarily +-- compute the precise new path. It should work correctly for polyline +-- paths, but not for curved paths. +-- +-- @param padding A padding distance. +-- @return The padded path. +-- + +function Path:pad(padding) + + local padded = self:clone() + padded:makeRigid() + + if padding == 0 then + return padded + end + + -- First, decompose the path into subpaths: + local subpaths = {} + local subpath = {} + local start_index = 1 + + local function closepath(end_index) + if #subpath >= 1 then + subpath.start_index = start_index + subpath.end_index = end_index + start_index = end_index + 1 + + local start = 1 + if (subpath[#subpath] - subpath[1]):norm() < 0.01 and subpath[2] then + start = 2 + subpath.skipped = subpath[1] + end + subpath[#subpath + 1] = subpath[start] + subpath[#subpath + 1] = subpath[start+1] + subpaths[#subpaths + 1] = subpath + subpath = {} + end + end + + for i,p in ipairs(padded) do + if p ~= "closepath" then + if type(p) == "table" then + subpath[#subpath + 1] = p + end + else + closepath (i) + end + end + closepath(#padded) + + -- Second, iterate over the subpaths: + for _,subpath in ipairs(subpaths) do + local new_coordinates = {} + local _,_,_,_,c_x,c_y = Coordinate.boundingBox(subpath) + local c = Coordinate.new(c_x,c_y) + + -- Find out the orientation of the path + local count = 0 + for i=1,#subpath-2 do + local d2 = subpath[i+1] - subpath[i] + local d1 = subpath[i+2] - subpath[i+1] + + local diff = math.atan2(d2.y,d2.x) - math.atan2(d1.y,d1.x) + + if diff < -math.pi then + count = count + 1 + elseif diff > math.pi then + count = count - 1 + end + end + + for i=2,#subpath-1 do + local p = subpath[i] + local d1 = subpath[i] - subpath[i-1] + local d2 = subpath[i+1] - subpath[i] + + local orth1 = Coordinate.new(-d1.y, d1.x) + local orth2 = Coordinate.new(-d2.y, d2.x) + + orth1:normalize() + orth2:normalize() + + if count < 0 then + orth1:scale(-1) + orth2:scale(-1) + end + + -- Ok, now we want to compute the intersection of the lines + -- perpendicular to p + padding*orth1 and p + padding*orth2: + + local det = orth1.x * orth2.y - orth1.y * orth2.x + + local c + if math.abs(det) < 0.1 then + c = orth1 + orth2 + c:scale(padding/2) + else + c = Coordinate.new (padding*(orth2.y-orth1.y)/det, padding*(orth1.x-orth2.x)/det) + end + + new_coordinates[i] = c+p + end + + for i=2,#subpath-1 do + local p = subpath[i] + local new_p = new_coordinates[i] + p.x = new_p.x + p.y = new_p.y + end + + if subpath.skipped then + local p = subpath[1] + local new_p = new_coordinates[#subpath-2] + p.x = new_p.x + p.y = new_p.y + end + + -- Now, we need to correct the curveto fields: + for i=subpath.start_index,subpath.end_index do + if self[i] == 'curveto' then + local from = rigid(self[i-1]) + local s1 = rigid(self[i+1]) + local s2 = rigid(self[i+2]) + local to = rigid(self[i+3]) + + local p1x, p1y, _, _, h1x, h1y = + Bezier.atTime(from.x, from.y, s1.x, s1.y, s2.x, s2.y, + to.x, to.y, 1/3) + + local p2x, p2y, _, _, _, _, h2x, h2y = + Bezier.atTime(from.x, from.y, s1.x, s1.y, s2.x, s2.y, + to.x, to.y, 2/3) + + local orth1 = Coordinate.new (p1y - h1y, -(p1x - h1x)) + orth1:normalize() + orth1:scale(-padding) + + local orth2 = Coordinate.new (p2y - h2y, -(p2x - h2x)) + orth2:normalize() + orth2:scale(padding) + + if count < 0 then + orth1:scale(-1) + orth2:scale(-1) + end + + local new_s1, new_s2 = + Bezier.supportsForPointsAtTime(padded[i-1], + Coordinate.new(p1x+orth1.x,p1y+orth1.y), 1/3, + Coordinate.new(p2x+orth2.x,p2y+orth2.y), 2/3, + padded[i+3]) + + padded[i+1] = new_s1 + padded[i+2] = new_s2 + end + end + end + + return padded +end + + + +--- +-- Appends an arc (as in the sense of ``a part of the circumference of +-- a circle'') to the path. You may optionally provide a +-- transformation matrix, which will be applied to the arc. In detail, +-- the following happens: We first invert the transformation +-- and apply it to the start point. Then we compute the arc +-- ``normally'', as if no transformation matrix were present. Then we +-- apply the transformation matrix to all computed points. +-- +-- @function Path:appendArc(start_angle,end_angle,radius,trans) +-- +-- @param start_angle The start angle of the arc. Must be specified in +-- degrees. +-- @param end_angle the end angle of the arc. +-- @param radius The radius of the circle on which this arc lies. +-- @param trans A transformation matrix. If |nil|, the identity +-- matrix will be assumed. + +Path.appendArc = lib.ondemand("Path_arced", Path, "appendArc") + + + +--- +-- Appends a clockwise arc (as in the sense of ``a part of the circumference of +-- a circle'') to the path such that it ends at a given point. If a +-- transformation matrix is given, both start and end point are first +-- transformed according to the inverted transformation, then the arc +-- is computed and then transformed back. +-- +-- @function Path:appendArcTo(target,radius_or_center,clockwise,trans) +-- +-- @param target The point where the arc should end. +-- @param radius_or_center If a number, it is the radius of the circle +-- on which this arc lies. If it is a |Coordinate|, this is the center +-- of the circle. +-- @param clockwise If true, the arc will be clockwise. Otherwise (the +-- default, if nothing or |nil| is given), the arc will be counter +-- clockwise. +-- @param trans A transformation matrix. If missing, +-- the identity matrix is assumed. + +Path.appendArcTo = lib.ondemand("Path_arced", Path, "appendArcTo") + + + + +-- +-- @return The Path as string. +-- +function Path:__tostring() + local r = {} + local i = 1 + while i <= #self do + local p = self[i] + + if p == "lineto" then + r [#r+1] = " -- " .. tostring(rigid(self[i+1])) + i = i + 1 + elseif p == "moveto" then + r [#r+1] = " " .. tostring(rigid(self[i+1]) ) + i = i + 1 + elseif p == "curveto" then + r [#r+1] = " .. controls " .. tostring(rigid(self[i+1])) .. " and " .. + tostring(rigid(self[i+2])) .. " .. " .. tostring(rigid(self[i+3])) + i = i + 3 + elseif p == "closepath" then + r [#r+1] = " -- cycle" + else + error("illegal path command") + end + i = i + 1 + end + return table.concat(r) +end + + + +-- Done + +return Path diff --git a/graphics/pgf/base/tex/generic/graphdrawing/lua/pgf/gd/model/Path_arced.lua b/graphics/pgf/base/tex/generic/graphdrawing/lua/pgf/gd/model/Path_arced.lua new file mode 100644 index 0000000000..1875b8d1a5 --- /dev/null +++ b/graphics/pgf/base/tex/generic/graphdrawing/lua/pgf/gd/model/Path_arced.lua @@ -0,0 +1,316 @@ +-- Copyright 2014 by Till Tantau +-- +-- This file may be distributed an/or modified +-- +-- 1. under the LaTeX Project Public License and/or +-- 2. under the GNU Public License +-- +-- See the file doc/generic/pgf/licenses/LICENSE for more information + +-- @release $Header$ + + +local Path = require 'pgf.gd.model.Path' + +-- Imports + +local Coordinate = require "pgf.gd.model.Coordinate" +local Transform = require "pgf.gd.lib.Transform" + + + +-- Locals + +local rigid = Path.rigid + +local tan = math.tan +local sin = math.sin +local cos = math.cos +local sqrt = math.sqrt +local atan2 = math.atan2 +local abs = math.abs + +local to_rad = math.pi/180 +local to_deg = 180/math.pi +local pi_half = math.pi/2 + +local function sin_quarter(x) + x = x % 360 + if x == 0 then + return 0 + elseif x == 90 then + return 1 + elseif x == 180 then + return 0 + else + return -1 + end +end + +local function cos_quarter(x) + x = x % 360 + if x == 0 then + return 1 + elseif x == 90 then + return 0 + elseif x == 180 then + return -1 + else + return 0 + end +end + +local function atan2deg(y,x) + + -- Works like atan2, but returns the angle in degrees and, returns + -- exactly a multiple of 90 if x or y are zero + + if x == 0 then + if y < 0 then + return -90 + else + return 90 + end + elseif y == 0 then + if x < 0 then + return 180 + else + return 0 + end + else + return atan2(y,x) * to_deg + end + +end + +local function subarc (path, startx, starty, start_angle, delta, radius, trans, center_x, center_y) + + local end_angle = start_angle + delta + local factor = tan (delta*to_rad/4) * 1.333333333333333333333 * radius + + local s1, c1, s190, c190, s2, c2, s290, c290 + + if start_angle % 90 == 0 then + s1, c1, s190, c190 = sin_quarter(start_angle), cos_quarter(start_angle), sin_quarter(start_angle+90), cos_quarter(start_angle+90) + else + local a1 = start_angle*to_rad + s1, c1, s190, c190 = sin(a1), cos(a1), sin(a1+pi_half), cos(a1+pi_half) + end + + if end_angle % 90 == 0 then + s2, c2, s290, c290 = sin_quarter(end_angle), cos_quarter(end_angle), sin_quarter(end_angle-90), cos_quarter(end_angle-90) + else + local a2 = end_angle * to_rad + s2, c2, s290, c290 = sin(a2), cos(a2), sin(a2-pi_half), cos(a2-pi_half) + end + + local lastx, lasty = center_x + c2*radius, center_y + s2*radius + + path[#path + 1] = "curveto" + path[#path + 1] = Coordinate.new (startx + c190*factor, starty + s190*factor) + path[#path + 1] = Coordinate.new (lastx + c290*factor, lasty + s290*factor) + path[#path + 1] = Coordinate.new (lastx, lasty) + + if trans then + path[#path-2]:apply(trans) + path[#path-1]:apply(trans) + path[#path ]:apply(trans) + end + + return lastx, lasty, end_angle +end + + + +local function arc (path, start, start_angle, end_angle, radius, trans, centerx, centery) + + -- @param path is the path object + -- @param start is the start coordinate + -- @param start_angle is given in degrees + -- @param end_angle is given in degrees + -- @param radius is the radius + -- @param trans is an optional transformation matrix that gets applied to all computed points + -- @param centerx optionally: x-part of the center of the circle + -- @param centery optionally: y-part of the center of the circle + + local startx, starty = start.x, start.y + + -- Compute center: + centerx = centerx or startx - cos(start_angle*to_rad)*radius + centery = centery or starty - sin(start_angle*to_rad)*radius + + if start_angle < end_angle then + -- First, ensure that the angles are in a reasonable range: + start_angle = start_angle % 360 + end_angle = end_angle % 360 + + if end_angle <= start_angle then + -- In case the modulo has inadvertently moved the end angle + -- before the start angle: + end_angle = end_angle + 360 + end + + -- Ok, now create a series of arcs that are at most quarter-cycles: + while start_angle < end_angle do + if start_angle + 179 < end_angle then + -- Add a quarter cycle: + startx, starty, start_angle = subarc(path, startx, starty, start_angle, 90, radius, trans, centerx, centery) + elseif start_angle + 90 < end_angle then + -- Add 60 degrees to ensure that there are no small segments + -- at the end + startx, starty, start_angle = subarc(path, startx, starty, start_angle, (end_angle-start_angle)/2, radius, trans, centerx, centery) + else + subarc(path, startx, starty, start_angle, end_angle - start_angle, radius, trans, centerx, centery) + break + end + end + + elseif start_angle > end_angle then + -- First, ensure that the angles are in a reasonable range: + start_angle = start_angle % 360 + end_angle = end_angle % 360 + + if end_angle >= start_angle then + -- In case the modulo has inadvertedly moved the end angle + -- before the start angle: + end_angle = end_angle - 360 + end + + -- Ok, now create a series of arcs that are at most quarter-cycles: + while start_angle > end_angle do + if start_angle - 179 > end_angle then + -- Add a quarter cycle: + startx, starty, start_angle = subarc(path, startx, starty, start_angle, -90, radius, trans, centerx, centery) + elseif start_angle - 90 > end_angle then + -- Add 60 degrees to ensure that there are no small segments + -- at the end + startx, starty, start_angle = subarc(path, startx, starty, start_angle, (end_angle-start_angle)/2, radius, trans, centerx, centery) + else + subarc(path, startx, starty, start_angle, end_angle - start_angle, radius, trans, centerx, centery) + break + end + end + + -- else, do nothing + end +end + + +-- Doc see Path.lua + +function Path:appendArc(start_angle,end_angle,radius, trans) + + local start = rigid(self[#self]) + assert(type(start) == "table", "trying to append an arc to a path that does not end with a coordinate") + + if trans then + start = start:clone() + start:apply(Transform.invert(trans)) + end + + arc (self, start, start_angle, end_angle, radius, trans) +end + + + + +-- Doc see Path.lua + +function Path:appendArcTo (target, radius_or_center, clockwise, trans) + + local start = rigid(self[#self]) + assert(type(start) == "table", "trying to append an arc to a path that does not end with a coordinate") + + local trans_target = target + local centerx, centery, radius + + if type(radius_or_center) == "number" then + radius = radius_or_center + else + centerx, centery = radius_or_center.x, radius_or_center.y + end + + if trans then + start = start:clone() + trans_target = target:clone() + local itrans = Transform.invert(trans) + start:apply(itrans) + trans_target:apply(itrans) + if centerx then + local t = radius_or_center:clone() + t:apply(itrans) + centerx, centery = t.x, t.y + end + end + + if not centerx then + -- Compute center + local dx, dy = target.x - start.x, target.y - start.y + + if abs(dx) == abs(dy) and abs(dx) == radius then + if (dx < 0 and dy < 0) or (dx > 0 and dy > 0) then + centerx = start.x + centery = trans_target.y + else + centerx = trans_target.x + centery = start.y + end + else + local l_sq = dx*dx + dy*dy + if l_sq >= radius*radius*4*0.999999 then + centerx = (start.x+trans_target.x) / 2 + centery = (start.y+trans_target.y) / 2 + assert(l_sq <= radius*radius*4/0.999999, "radius too small for arc") + else + -- Normalize + local l = sqrt(l_sq) + local nx = dx / l + local ny = dy / l + + local e = sqrt(radius*radius - 0.25*l_sq) + + centerx = start.x + 0.5*dx - ny*e + centery = start.y + 0.5*dy + nx*e + end + end + end + + local start_dx, start_dy, target_dx, target_dy = + start.x - centerx, start.y - centery, + trans_target.x - centerx, trans_target.y - centery + + if not radius then + -- Center is given, compute radius: + radius_sq = start_dx^2 + start_dy^2 + + -- Ensure that the circle is, indeed, centered: + assert (abs(target_dx^2 + target_dy^2 - radius_sq)/radius_sq < 1e-5, "attempting to add an arc with incorrect center") + + radius = sqrt(radius_sq) + end + + -- Compute start and end angle: + local start_angle = atan2deg(start_dy, start_dx) + local end_angle = atan2deg(target_dy, target_dx) + + if clockwise then + if end_angle > start_angle then + end_angle = end_angle - 360 + end + else + if end_angle < start_angle then + end_angle = end_angle + 360 + end + end + + arc (self, start, start_angle, end_angle, radius, trans, centerx, centery) + + -- Patch last point to avoid rounding problems: + self[#self] = target +end + + + +-- Done + +return true diff --git a/graphics/pgf/base/tex/generic/graphdrawing/lua/pgf/gd/model/Vertex.lua b/graphics/pgf/base/tex/generic/graphdrawing/lua/pgf/gd/model/Vertex.lua new file mode 100644 index 0000000000..677ea4fbd0 --- /dev/null +++ b/graphics/pgf/base/tex/generic/graphdrawing/lua/pgf/gd/model/Vertex.lua @@ -0,0 +1,296 @@ +-- Copyright 2012 by Till Tantau +-- +-- This file may be distributed an/or modified +-- +-- 1. under the LaTeX Project Public License and/or +-- 2. under the GNU Public License +-- +-- See the file doc/generic/pgf/licenses/LICENSE for more information + +-- @release $Header$ + + +--- +-- A |Vertex| instance models a node of graphs. Each |Vertex| object can be an +-- element of any number of graphs (whereas an |Arc| object can only be an +-- element of a single graph). +-- +-- When a vertex is added to a digraph |g|, two tables are created in +-- the vertex' storage: An array of incoming arcs (with respect to +-- |g|) and an array of outgoing arcs (again, with respect to +-- |g|). The fields are managed by the |Digraph| class and should not +-- be modified directly. +-- +-- Note that a |Vertex| is an abstraction of \tikzname\ nodes; indeed +-- the objective is to ensure that, in principle, we can use them +-- independently of \TeX. For this reason, you will not find any +-- references to |tex| inside a |Vertex|; this information is only +-- available in the syntactic digraph. +-- +-- One important aspect of vertices are its anchors -- a concept well +-- familiar for users of \tikzname, but since we need to abstract from +-- \tikzname, a separate anchor management is available inside the +-- graph drawing system. It works as follows: +-- +-- First of all, every vertex has a path, which is a (typically +-- closed) line around the vertex. The display system will pass down +-- the vertex' path to the graph drawing system and this path will be +-- stored as a |Path| object in the |path| field of the vertex. This +-- path lives in a special ``local'' coordinate system, that is, all +-- coordinates of this path should actually be considered relative to +-- the vertex' |pos| field. Note that the path is typically, but not +-- always, ``centered'' on the origin. A graph drawing algorithm +-- should arrange the vertices in such a way that the origins in the +-- path coordinate systems are aligned. +-- +-- To illustrate the difference between the origin and the vertex +-- center, consider a tree drawing algorithm in which a node |root| has +-- three children |a|, |b|, and |g|. Now, if we were to simply center +-- these three letters vertically and arrange them in a line, the +-- letters would appear to ``jump up and down'' since the height of +-- the three letters are quite different. A solution is to shift the +-- letters (and, thus, the paths of the vertices) in such a way that +-- in all three letters the baseline of the letters is exactly at the +-- origin. Now, when a graph drawing algorithm aligns these vertices +-- along the origins, the letters will all have the same baseline. +-- +-- Apart from the origin, there may be other positions in the path +-- coordinate system that are of interest -- such as the center of +-- the vertex. As mentioned above, this need not be the origin and +-- although a graph drawing algorithm should align the origins, +-- \emph{edges} between vertices should head toward these vertex +-- centers rather that toward the origins. Other points of interest +-- might be the ``top'' of the node. +-- +-- All points of special interest are called ``anchors''. The |anchor| +-- method allows you to retrieve them. By default, you always have +-- access to the |center| anchor, but other anchors may or may not be +-- available also, see the |anchor| method for details. +-- +-- @field pos A coordinate object that stores the position where the +-- vertex should be placed on the canvas. The main objective of graph drawing +-- algorithms is to update this coordinate. +-- +-- @field name An optional string that is used as a textual representation +-- of the node. +-- +-- @field path The path of the vertex's shape. This is a path along +-- the outer line resulting from stroking the vertex's original +-- shape. For instance, if you have a quadratic shape of size 1cm and +-- you stroke the path with a pen of 2mm thickness, this |path| field +-- would store a path of a square of edge length 12mm. +-- +-- @field anchors A table of anchors (in the TikZ sense). The table is +-- indexed by the anchor names (strings) and the values are +-- |Coordinate|s. Currently, it is only guaranteed that the |center| +-- anchor is present. Note that the |center| anchor need not lie at +-- the origin: A graph drawing system should align nodes relative to +-- the origin of the path's coordinate system. However, lines going to +-- and from the node will head towards the |center| anchor. See +-- Section~\ref{section-gd-anchors} for details. +-- +-- @field options A table of options that contains user-defined options. +-- +-- @field animations An array of attribute animations for the +-- node. When an algorithm adds entries to this array, the display +-- layer should try to render these. The syntax is as follows: Each +-- element in the array is a table with a field |attribute|, which must +-- be a string like |"opacity"| or |"translate"|, a field |entries|, +-- which must be an array to be explained in a moment, and field +-- |options|, which must be a table of the same syntax as the +-- |options| field. For the |entries| array, each element must be +-- table with two field: |t| must be set to a number, representing a +-- time in seconds, and |value|, which must be set to a value that +-- the |attribute| should have at the given time. The entries and the +-- options will then be interpreted as described in \pgfname's basic +-- layer animation system, except that where a |\pgfpoint| is expected +-- you provide a |Coordinate| and a where a path is expected you +-- provide a |Path|. +-- +-- @field shape A string describing the shape of the node (like |rectangle| +-- or |circle|). Note, however, that this is more ``informative''; the +-- actual information that is used by the graph drawing system for +-- determining the extent of a node, its bounding box, convex hull, +-- and line intersections is the |path| field. +-- +-- @field kind A string describing the kind of the node. For instance, a +-- node of type |"dummy"| does not correspond to any real node in +-- the graph but is used by the graph drawing algorithm. +-- +-- @field event The |Event| when this vertex was created (may be |nil| +-- if the vertex is not part of the syntactic digraph). +-- +-- @field incomings A table indexed by |Digraph| objects. For each +-- digraph, the table entry is an array of all vertices from which +-- there is an |Arc| to this vertex. This field is internal and may +-- not only be accessed by the |Digraph| class. +-- +-- @field outgoings Like |incomings|, but for outgoing arcs. +-- +local Vertex = {} +Vertex.__index = Vertex + + +-- Namespace + +require("pgf.gd.model").Vertex = Vertex + + +-- Imports + +local Coordinate = require "pgf.gd.model.Coordinate" +local Path = require "pgf.gd.model.Path" +local Storage = require "pgf.gd.lib.Storage" + + +--- +-- Create a new vertex. The |initial| parameter allows you to setup +-- some initial values. +-- +-- @usage +--\begin{codeexample}[code only, tikz syntax=false] +--local v = Vertex.new { name = "hello", pos = Coordinate.new(1,1) } +--\end{codeexample} +-- +-- @param initial Values to override default node settings. The +-- following are permissible: +-- \begin{description} +-- \item[|pos|] Initial position of the node. +-- \item[|name|] The name of the node. It is optional to define this. +-- \item[|path|] A |Path| object representing the vertex's hull. +-- \item[|anchors|] A table of anchors. +-- \item[|options|] An options table for the vertex. +-- \item[|animations|] An array of generated animation attributes. +-- \item[|shape|] A string describing the shape. If not given, |"none"| is used. +-- \item[|kind|] A kind like |"node"| or |"dummy"|. If not given, |"dummy"| is used. +-- \end{description} +-- +-- @return A newly allocated node. +-- +function Vertex.new(values) + local new = { + incomings = Storage.new(), + outgoings = Storage.new() + } + for k,v in pairs(values) do + new[k] = v + end + new.path = new.path or Path.new { 0, 0 } + new.shape = new.shape or "none" + new.kind = new.kind or "dummy" + new.pos = new.pos or Coordinate.new(0,0) + new.anchors = new.anchors or { center = Coordinate.new(0,0) } + new.animations = new.animations or {} + return setmetatable (new, Vertex) +end + + + + +--- +-- Returns a bounding box of a vertex. +-- +-- @return |min_x| The minimum $x$ value of the bounding box of the path +-- @return |min_y| The minimum $y$ value +-- @return |max_x| +-- @return |max_y| +-- @return |center_x| The center of the bounding box +-- @return |center_y| + +function Vertex:boundingBox() + return self.path:boundingBox() +end + + + +local anchor_cache = Storage.new () + +local directions = { + north = function(min_x, min_y, max_x, max_y) + return (min_x+max_x)/2, max_y + end, + south = function(min_x, min_y, max_x, max_y) + return (min_x+max_x)/2, min_y + end, + east = function(min_x, min_y, max_x, max_y) + return max_x, (min_y+max_y)/2 + end, + west = function(min_x, min_y, max_x, max_y) + return min_x, (min_y+max_y)/2 + end, + ["north west"] = function(min_x, min_y, max_x, max_y) + return min_x, max_y + end, + ["north east"] = function(min_x, min_y, max_x, max_y) + return max_x, max_y + end, + ["south west"] = function(min_x, min_y, max_x, max_y) + return min_x, min_y + end, + ["south east"] = function(min_x, min_y, max_x, max_y) + return max_x, min_y + end, +} + +--- +-- Returns an anchor position in a vertex. First, we try to look +-- the anchor up in the vertex's |anchors| table. If it is not found +-- there, we test whether it is one of the direction strings |north|, +-- |south east|, and so on. If so, we consider a line from the center +-- of the node to the position on the bounding box that corresponds to +-- the given direction (so |south east| would be the lower right +-- corner). We intersect this line with the vertex's path and return +-- the result. Finally, if the above fails, we try to consider the +-- anchor as a number and return the intersection of a line starting +-- at the vertex's center with the number as its angle and the path of +-- the vertex. +-- +-- @param anchor An anchor as detailed above +-- @return A coordinate in the vertex's local coordinate system (so +-- add the |pos| field to arrive at the actual position). If the +-- anchor was not found, |nil| is returned + +function Vertex:anchor(anchor) + local c = self.anchors[anchor] + if not c then + local b + local d = directions [anchor] + if d then + b = Coordinate.new(d(self:boundingBox())) + else + local n = tonumber(anchor) + if n then + local x1, y1, x2, y2 = self:boundingBox() + local r = math.max(x2-x1, y2-y1) + b = Coordinate.new(r*math.cos(n/180*math.pi),r*math.sin(n/180*math.pi)) + b:shiftByCoordinate(self.anchors.center) + end + end + if not b then + return + end + local p = Path.new {'moveto', self.anchors.center, 'lineto', b} + local intersections = p:intersectionsWith(self.path) + if #intersections > 0 then + c = intersections[1].point + end + end + self.anchors[anchor] = c + return c +end + + + +-- +-- Returns a string representation of a vertex. This is mainly for debugging +-- +-- @return The Arc as string. +-- +function Vertex:__tostring() + return self.name or tostring(self.anchors) +end + + +-- Done + +return Vertex diff --git a/graphics/pgf/base/tex/generic/graphdrawing/lua/pgf/gd/model/library.lua b/graphics/pgf/base/tex/generic/graphdrawing/lua/pgf/gd/model/library.lua new file mode 100644 index 0000000000..ad62faa0f3 --- /dev/null +++ b/graphics/pgf/base/tex/generic/graphdrawing/lua/pgf/gd/model/library.lua @@ -0,0 +1,15 @@ +-- Copyright 2012 by Till Tantau +-- +-- This file may be distributed an/or modified +-- +-- 1. under the LaTeX Project Public License and/or +-- 2. under the GNU Public License +-- +-- See the file doc/generic/pgf/licenses/LICENSE for more information + +-- @release $Header$ + + + +-- Load declarations from: +require "pgf.gd.model.Hyperedge" diff --git a/graphics/pgf/base/tex/generic/graphdrawing/lua/pgf/gd/ogdf.lua b/graphics/pgf/base/tex/generic/graphdrawing/lua/pgf/gd/ogdf.lua new file mode 100644 index 0000000000..f7905f2418 --- /dev/null +++ b/graphics/pgf/base/tex/generic/graphdrawing/lua/pgf/gd/ogdf.lua @@ -0,0 +1,26 @@ +-- Copyright 2012 by Till Tantau +-- +-- This file may be distributed an/or modified +-- +-- 1. under the LaTeX Project Public License and/or +-- 2. under the GNU Public License +-- +-- See the file doc/generic/pgf/licenses/LICENSE for more information + +--- @release $Header$ + + + +-- Imports + +require "pgf" +require "pgf.gd" + + +-- Declare namespace +pgf.gd.ogdf = {} + + +-- Done + +return pgf.gd.ogdf
\ No newline at end of file diff --git a/graphics/pgf/base/tex/generic/graphdrawing/lua/pgf/gd/ogdf/library.lua b/graphics/pgf/base/tex/generic/graphdrawing/lua/pgf/gd/ogdf/library.lua new file mode 100644 index 0000000000..f91c75a4d6 --- /dev/null +++ b/graphics/pgf/base/tex/generic/graphdrawing/lua/pgf/gd/ogdf/library.lua @@ -0,0 +1,33 @@ +-- Copyright 2012 by Till Tantau +-- +-- This file may be distributed an/or modified +-- +-- 1. under the LaTeX Project Public License and/or +-- 2. under the GNU Public License +-- +-- See the file doc/generic/pgf/licenses/LICENSE for more information + +-- @release $Header$ + + +--- +-- The Open Graph Drawing Framework (\textsc{ogdf}) is a large, +-- powerful graph drawing system written in C++. This library enables +-- its use inside \tikzname's graph drawing system by translating +-- back-and-forth between Lua and C++. +-- +-- Since C++ code is compiled and not interpreted (like Lua), in order +-- to use the present library, you need a compiled version of the +-- \pgfname\ interface code for the \textsc{ogdf} library +-- (|pgf/gd/ogdf/c/ogdf_script.so|) installed correctly for your particular +-- architecture. This is by no means trivial\dots +-- +-- @library + +local ogdf + + +-- Load the C++ code: + +require "pgf_gd_ogdf_c_ogdf_script" + diff --git a/graphics/pgf/base/tex/generic/graphdrawing/lua/pgf/gd/pedigrees.lua b/graphics/pgf/base/tex/generic/graphdrawing/lua/pgf/gd/pedigrees.lua new file mode 100644 index 0000000000..95689e6379 --- /dev/null +++ b/graphics/pgf/base/tex/generic/graphdrawing/lua/pgf/gd/pedigrees.lua @@ -0,0 +1,21 @@ +-- Copyright 2015 by Till Tantau +-- +-- This file may be distributed an/or modified +-- +-- 1. under the LaTeX Project Public License and/or +-- 2. under the GNU Public License +-- +-- See the file doc/generic/pgf/licenses/LICENSE for more information + +--- @release $Header$ + + +local pedigrees = {} + +-- Declare namespace +require("pgf.gd").pedigrees = tree + + +-- Done + +return pedigrees
\ No newline at end of file diff --git a/graphics/pgf/base/tex/generic/graphdrawing/lua/pgf/gd/pedigrees/Koerner2015.lua b/graphics/pgf/base/tex/generic/graphdrawing/lua/pgf/gd/pedigrees/Koerner2015.lua new file mode 100644 index 0000000000..32927530a2 --- /dev/null +++ b/graphics/pgf/base/tex/generic/graphdrawing/lua/pgf/gd/pedigrees/Koerner2015.lua @@ -0,0 +1,163 @@ +-- Copyright 2015 by Till Tantau +-- +-- This file may be distributed an/or modified +-- +-- 1. under the LaTeX Project Public License and/or +-- 2. under the GNU Public License +-- +-- See the file doc/generic/pgf/licenses/LICENSE for more information + +-- @release $Header$ + + +local Koerner2015 = {} + + +-- Namespace +require("pgf.gd.pedigrees").Koerner2015 = Koerner2015 + +-- Imports +local InterfaceToAlgorithms = require "pgf.gd.interface.InterfaceToAlgorithms" +local Storage = require "pgf.gd.lib.Storage" +local Direct = require "pgf.gd.lib.Direct" + +-- Shorthand: +local declare = InterfaceToAlgorithms.declare + + +--- +declare { + key = "mate", + type = "boolean", + + summary = [[" + Edges of type |mate| join mates. + "]], +} + + +--- +declare { + key = "child", + type = "boolean", + + summary = [[" + Edges of type |child| join a parent to a child. The parent is the tail + of the edge, the child is the head. + "]], +} + +--- +declare { + key = "sibling", + type = "boolean", + + summary = [[" + Edges of type |sibling| join a siblings (persons with identical parents). + "]], +} + + +--- +declare { + key = "simple pedigree layout", + algorithm = Koerner2015, + + postconditions = { + upward_oriented = true + }, + + summary = [[" + A simple algorithm for drawing a pedigree. + "]], + documentation = [[" + ... + "]], + examples = [[" + \tikz \graph [simple pedigree layout, default edge operator=complete bipartite] + { + Eve -- [mate] Felix; + { Eve, Felix } -> [child] { George, Hank }; + + Alice -- [mate] Bob; + { Alice, Bob } -> [child] { Charly, Dave, Eve }; + }; + "]] +} + + +function Koerner2015:run() + + local g = self.digraph + + -- Compute ranks: + + local visited = {} + local ranks = {} + + local queue = { { g.vertices[1], 1 } } + + local queue_start = 1 + local queue_end = 1 + + local function put(v, r) + queue_end = queue_end + 1 + queue [queue_end] = { v, r } + end + + local function get() + local v = queue[queue_start][1] + local r = queue[queue_start][2] + queue_start = queue_start + 1 + return v,r + end + + while queue_start <= queue_end do + + -- Pop + local v, rank = get() + ranks[v] = rank + + visited [v] = true + + -- Follow mates: + for _,a in ipairs(g:outgoing(v)) do + if a:options("sibling") then + if not visited[a.head] then + put(a.head, rank) + end + end + end + for _,a in ipairs(g:incoming(v)) do + if a:options("child") then + if not visited[a.tail] then + put(a.tail, rank-1) + end + end + end + for _,a in ipairs(g:outgoing(v)) do + if a:options("child") then + if not visited[a.head] then + put(a.head, rank+1) + end + end + end + for _,a in ipairs(g:outgoing(v)) do + if a:options("mate") then + if not visited[a.head] then + put(a.head, rank) + end + end + end + end + + for i,v in ipairs(g.vertices) do + v.pos.x = i*50 + v.pos.y = ranks[v] * 50 + end + +end + +return Koerner2015 + + diff --git a/graphics/pgf/base/tex/generic/graphdrawing/lua/pgf/gd/pedigrees/library.lua b/graphics/pgf/base/tex/generic/graphdrawing/lua/pgf/gd/pedigrees/library.lua new file mode 100644 index 0000000000..db3d4f9cf7 --- /dev/null +++ b/graphics/pgf/base/tex/generic/graphdrawing/lua/pgf/gd/pedigrees/library.lua @@ -0,0 +1,21 @@ +-- Copyright 2015 by Till Tantau +-- +-- This file may be distributed an/or modified +-- +-- 1. under the LaTeX Project Public License and/or +-- 2. under the GNU Public License +-- +-- See the file doc/generic/pgf/licenses/LICENSE for more information + +-- @release $Header$ + + + +--- +-- A pedigree depicts parent--mate--sibling relationships between individuals. +-- +-- @library + +local pedigrees -- Library name + +require "pgf.gd.pedigrees.Koerner2015" diff --git a/graphics/pgf/base/tex/generic/graphdrawing/lua/pgf/gd/phylogenetics.lua b/graphics/pgf/base/tex/generic/graphdrawing/lua/pgf/gd/phylogenetics.lua new file mode 100644 index 0000000000..77c66de5b2 --- /dev/null +++ b/graphics/pgf/base/tex/generic/graphdrawing/lua/pgf/gd/phylogenetics.lua @@ -0,0 +1,21 @@ +-- Copyright 2012 by Till Tantau +-- +-- This file may be distributed an/or modified +-- +-- 1. under the LaTeX Project Public License and/or +-- 2. under the GNU Public License +-- +-- See the file doc/generic/pgf/licenses/LICENSE for more information + +--- @release $Header$ + + +local phylogenetics = {} + +-- Declare namespace +require("pgf.gd").phylogenetics = tree + + +-- Done + +return phylogenetics
\ No newline at end of file diff --git a/graphics/pgf/base/tex/generic/graphdrawing/lua/pgf/gd/phylogenetics/AuthorDefinedPhylogeny.lua b/graphics/pgf/base/tex/generic/graphdrawing/lua/pgf/gd/phylogenetics/AuthorDefinedPhylogeny.lua new file mode 100644 index 0000000000..04e1ca49f1 --- /dev/null +++ b/graphics/pgf/base/tex/generic/graphdrawing/lua/pgf/gd/phylogenetics/AuthorDefinedPhylogeny.lua @@ -0,0 +1,77 @@ +-- Copyright 2013 by Till Tantau +-- +-- This file may be distributed an/or modified +-- +-- 1. under the LaTeX Project Public License and/or +-- 2. under the GNU Public License +-- +-- See the file doc/generic/pgf/licenses/LICENSE for more information + +-- @release $Header$ + + + + +local AuthorDefinedPhylogeny = {} + + +-- Namespace +require("pgf.gd.phylogenetics").AuthorDefinedPhylogeny = AuthorDefinedPhylogeny + +-- Imports +local InterfaceToAlgorithms = require "pgf.gd.interface.InterfaceToAlgorithms" +local Direct = require "pgf.gd.lib.Direct" + +-- Shorthand: +local declare = InterfaceToAlgorithms.declare + + +--- +declare { + key = "phylogenetic tree by author", + algorithm = AuthorDefinedPhylogeny, + phase = "phylogenetic tree generation", + phase_default = true, + + summary = [[" + When this key is used, the phylogenetic tree must be specified + by the author (rather than being generated algorithmically). + "]], + documentation = [[" + A spanning tree of the input graph will be computed first (it + must be connected, otherwise errors will result). + The evolutionary length of the edges must be specified through + the use of the |length| key for each edge. + "]], + examples = [[" + \tikz \graph [phylogenetic tree layout] { + a -- { + b [>length=2] --[length=1] { c, d }, + e [>length=3] + } + }; + "]] +} + + + +function AuthorDefinedPhylogeny:run() + + local spanning_tree = self.main_algorithm.digraph.options.algorithm_phases["spanning tree computation"].new { + ugraph = self.main_algorithm.ugraph, + events = {} -- no events + }:run() + + local phylogenetic_tree = Direct.ugraphFromDigraph(spanning_tree) + local lengths = self.lengths + + for _,a in ipairs(phylogenetic_tree.arcs) do + lengths[a.tail][a.head] = a:options('length') + end + + return phylogenetic_tree +end + + + +return AuthorDefinedPhylogeny diff --git a/graphics/pgf/base/tex/generic/graphdrawing/lua/pgf/gd/phylogenetics/BalancedMinimumEvolution.lua b/graphics/pgf/base/tex/generic/graphdrawing/lua/pgf/gd/phylogenetics/BalancedMinimumEvolution.lua new file mode 100644 index 0000000000..ef40f0ee60 --- /dev/null +++ b/graphics/pgf/base/tex/generic/graphdrawing/lua/pgf/gd/phylogenetics/BalancedMinimumEvolution.lua @@ -0,0 +1,593 @@ +-- Copyright 2013 by Sarah Mäusle and Till Tantau +-- +-- This file may be distributed an/or modified +-- +-- 1. under the LaTeX Project Public License and/or +-- 2. under the GNU Public License +-- +-- See the file doc/generic/pgf/licenses/LICENSE for more information + +-- @release $Header$ + + + +local BalancedMinimumEvolution = {} + + +-- Namespace +require("pgf.gd.phylogenetics").BalancedMinimumEvolution = BalancedMinimumEvolution + +-- Imports +local InterfaceToAlgorithms = require("pgf.gd.interface.InterfaceToAlgorithms") +local DistanceMatrix = require("pgf.gd.phylogenetics.DistanceMatrix") +local Storage = require("pgf.gd.lib.Storage") +local Digraph = require("pgf.gd.model.Digraph") +local lib = require("pgf.gd.lib") + +-- Shorthand: +local declare = InterfaceToAlgorithms.declare + + +--- +declare { + key = "balanced minimum evolution", + algorithm = BalancedMinimumEvolution, + phase = "phylogenetic tree generation", + + summary = [[" + The BME (Balanced Minimum Evolution) algorithm tries to minimize + the total tree length. + "]], + documentation = [[" + This algorithm is from Desper and Gascuel, \emph{Fast and + Accurate Phylogeny Reconstruction Algorithms Based on the + Minimum-Evolution Principle}, 2002. The tree is built in a way + that minimizes the total tree length. The leaves are inserted + into the tree one after another, creating new edges and new + nodes. After every insertion the distance matrix has to be + updated. + "]], + examples = [[" + \tikz \graph [phylogenetic tree layout, + balanced minimum evolution, + grow'=right, sibling distance=0pt, + distance matrix={ + 0 4 9 9 9 9 9 + 4 0 9 9 9 9 9 + 9 9 0 2 7 7 7 + 9 9 2 0 7 7 7 + 9 9 7 7 0 3 5 + 9 9 7 7 3 0 5 + 9 9 7 7 5 5 0}] + { a, b, c, d, e, f, g }; + "]] +} + + + + +function BalancedMinimumEvolution:run() + + self.tree = Digraph.new(self.main_algorithm.digraph) + + self.distances = Storage.newTableStorage() + + local vertices = self.tree.vertices + + -- Sanity checks: + if #vertices == 2 then + self.tree:connect(vertices[1],vertices[2]) + return self.tree + elseif #vertices > 2 then + + -- Setup storages: + self.is_leaf = Storage.new() + + -- First, build the initial distance matrix: + local matrix = DistanceMatrix.graphDistanceMatrix(self.tree) + + -- Store distance information in the distance fields of the storages: + for _,u in ipairs(vertices) do + for _,v in ipairs(vertices) do + self.distances[u][v] = matrix[u][v] + end + end + + -- Run BME + self:runBME() + + -- Run postoptimizations + local optimization_class = self.tree.options.algorithm_phases['phylogenetic tree optimization'] + optimization_class.new { + main_algorithm = self.main_algorithm, + tree = self.tree, + matrix = self.matrix, + distances = self.distances, + is_leaf = self.is_leaf, + }:run() + end + + -- Finish + self:computeFinalLengths() + self:createFinalEdges() + + return self.tree +end + + + + +-- the BME (Balanced Minimum Evolution) algorithm +-- [DESPER and GASCUEL: Fast and Accurate Phylogeny Reconstruction +-- Algorithms Based on the Minimum-Evolution Principle, 2002] +-- +-- The tree is built in a way that minimizes the total tree length. +-- The leaves are inserted into the tree one after another, creating new edges and new nodes. +-- After every insertion the distance matrix has to be updated. +function BalancedMinimumEvolution:runBME() + local g = self.tree + local leaves = {} + local is_leaf = self.is_leaf + local distances = self.distances + + -- get user input + for i, vertex in ipairs (g.vertices) do + leaves[i] = vertex + is_leaf[vertex] = true + end + + -- create the new node which will be connected to the first three leaves + local new_node = InterfaceToAlgorithms.createVertex( + self.main_algorithm, + { + name = "BMEnode"..#g.vertices+1, + generated_options = { { key = "phylogenetic inner node" } } + } + ) + g:add {new_node} + -- set the distances of new_node to subtrees + local distance_1_2 = self:distance(leaves[1],leaves[2]) + local distance_1_3 = self:distance(leaves[1],leaves[3]) + local distance_2_3 = self:distance(leaves[2],leaves[3]) + distances[new_node][leaves[1]] = 0.5*(distance_1_2 + distance_1_3) + distances[new_node][leaves[2]] = 0.5*(distance_1_2 + distance_2_3) + distances[new_node][leaves[3]] = 0.5*(distance_1_3 + distance_2_3) + + --connect the first three leaves to the new node + for i = 1,3 do + g:connect(new_node, leaves[i]) + g:connect(leaves[i], new_node) + end + + for k = 4,#leaves do + -- compute distance from k to any subtree + local k_dists = Storage.newTableStorage() + for i = 1,k-1 do + -- note that the function called stores the k_dists before they are overwritten + self:computeAverageDistancesToAllSubtreesForK(g.vertices[i], { }, k,k_dists) + end + + -- find the best insertion point + local best_arc = self:findBestEdge(g.vertices[1],nil,k_dists) + local head = best_arc.head + local tail = best_arc.tail + + -- remove the old arc + g:disconnect(tail, head) + g:disconnect(head, tail) + + -- create the new node + local new_node = InterfaceToAlgorithms.createVertex( + self.main_algorithm, + { + name = "BMEnode"..#g.vertices+1, + generated_options = { + { key = "phylogenetic inner node" } + } + } + ) + g:add{new_node} + + -- gather the vertices that will be connected to the new node... + local vertices_to_connect = { head, tail, leaves[k] } + + -- ...and connect them + for _, vertex in pairs (vertices_to_connect) do + g:connect(new_node, vertex) + g:connect(vertex, new_node) + end + + if not is_leaf[tail] then + distances[leaves[k]][tail] = k_dists[head][tail] + end + if not is_leaf[head] then + distances[leaves[k]][head] = k_dists[tail][head] + end + -- insert distances from k to subtrees into actual matrix... + self:setAccurateDistancesForK(new_node,nil,k,k_dists,leaves) + + -- set the distance from k to the new node, which was created by inserting k into the graph + distances[leaves[k]][new_node] = 0.5*( self:distance(leaves[k], head) + self:distance(leaves[k],tail)) + + -- update the average distances + local values = {} + values.s = head -- s--u is the arc into which k has been inserted + values.u = tail + values.new_node = new_node -- the new node created by inserting k + self:updateAverageDistances(new_node, values,k,leaves) + end +end + +-- +-- Updates the average distances from k to all subtrees +-- +-- @param vertex The starting point of the recursion +-- @param values The values needed for the recursion +-- - s, u The nodes which span the edge into which k has been +-- inserted +-- - new_node The new_node which has been created to insert k +-- - l (l-1) is the number of edges between the +-- new_node and the current subtree Y +-- +-- values.new_node, values.u and values.s must be set +-- the depth first search must begin at the new node, thus vertex +-- must be set to the newly created node +function BalancedMinimumEvolution:updateAverageDistances(vertex, values, k, leaves) + local g = self.tree + local leaf_k = leaves[k] + local y, z, x + if not values.visited then + values.visited = {} + values.visited[leaf_k] = leaf_k -- we don't want to visit k! + end + -- there are (l-1) edges between new_node and y + if not values.l then values.l = 1 end + if not values.new_node then values.new_node = g:outgoing(leaf_k)[1].head end + --values.s and values.u must be set + + -- the two nodes which connect the edge on which k was inserted: s,u + + local new_node = values.new_node + local l = values.l + local visited = values.visited + + visited[vertex] = vertex + + -- computes the distances to Y{k} for all subtrees X of Z + function loop_over_x( x, y, values ) + local l = values.l + local y1= values.y1 + + -- calculate distance between Y{k} and X + local old_distance -- the distance between Y{/k} and X needed for calculating the new distance + if y == new_node then -- this y didn't exist in the former tree; so use y1 (see below) + old_distance = self:distance(x,y1) + else + old_distance = self:distance(x,y) + end + + local new_distance = old_distance + math.pow(2,-l) * ( self:distance(leaf_k,x) - self:distance(x,y1) ) + self.distances[x][y] = new_distance + self.distances[y][x] = new_distance -- symmetric matrix + + values.x_visited[x] = x + --go deeper to next x + for _, x_arc in ipairs (self.tree:outgoing(x)) do + if not values.x_visited[x_arc.head] then + local new_x = x_arc.head + loop_over_x( new_x, y, values ) + end + end + end + + --loop over Z's + for _, arc in ipairs (self.tree:outgoing(vertex)) do + if not visited[arc.head] then + -- set y1, which is the node which was pushed further away from + -- subtree Z by inserting k + if arc.head == values.s then + values.y1 = values.u + elseif arc.head == values.u then + values.y1 = values.s + else + assert(values.y1,"no y1 set!") + end + + z = arc.head -- root of the subtree we're looking at + y = arc.tail -- the root of the subtree-complement of Z + + x = z -- the first subtree of Z is Z itself + values.x_visited = {} + values.x_visited[y] = y -- we don't want to go there, as we want to stay within Z + loop_over_x( z,y, values ) -- visit all possible subtrees of Z + + -- go to next Z + values.l = values.l+1 -- moving further away from the new_node + self:updateAverageDistances(z,values,k,leaves) + values.l = values.l-1 -- moving back to the new_node + end + end +end + + +-- +-- Computes the average distances of a node, which does not yet belong +-- to the graph, to all subtrees. This is done using a depth first +-- search +-- +-- @param vertex The starting point of the depth first search +-- @param values The values for the recursion +-- - distances The table in which the distances are to be +-- stored +-- - outgoing_arcs The table containing the outgoing arcs +-- of the current vertex +-- +-- @return The average distance of the new node #k to any subtree +-- The distances are stored as follows: +-- example: distances[center][a] +-- center is any vertex, thus if center is an inner vertex +-- it has 3 neighbors a,b and c, which can all be seen as the +-- roots of subtrees A,B,C. +-- distances[center][a] gives us the distance of the new +-- node k to the subtree A. +-- if center is a leaf, it has only one neighbor, which +-- can also be seen as the root of the subtree T\{center} +-- +function BalancedMinimumEvolution:computeAverageDistancesToAllSubtreesForK(vertex, values, k, k_dists) + local is_leaf = self.is_leaf + local arcs = self.tree.arcs + local vertices = self.tree.vertices + local center_vertex = vertex + -- for every vertex a table is created, in which the distances to all + -- its subtrees will be stored + + values.outgoing_arcs = values.outgoing_arcs or self.tree:outgoing(center_vertex) + for _, arc in ipairs (values.outgoing_arcs) do + local root = arc.head -- this vertex can be seen as the root of a subtree + if is_leaf[root] then -- we know the distance of k to the leaf! + k_dists[center_vertex][root] = self:distance(vertices[k], root) + else -- to compute the distance we need the root's neighboring vertices, which we can access by its outgoing arcs + local arc1, arc2 + local arc_back -- the arc we came from + for _, next_arc in ipairs (self.tree:outgoing(root)) do + if next_arc.head ~= center_vertex then + arc1 = arc1 or next_arc + arc2 = next_arc + else + arc_back = next_arc + end + end + + values.outgoing_arcs = { arc1, arc2, arc_back } + + -- go deeper, if the distances for the next center node haven't been set yet + if not (k_dists[root][arc1.head] and k_dists[root][arc2.head]) then + self:computeAverageDistancesToAllSubtreesForK(root, values, k,k_dists) + end + + -- set the distance between k and subtree + k_dists[center_vertex][root] = 1/2 * (k_dists[root][arc1.head] + k_dists[root][arc2.head]) + end + end +end + + +-- +-- Sets the distances from k to subtrees +-- In computeAverageDistancesToAllSubtreesForK the distances to ALL possible +-- subtrees are computed. Once k is inserted many of those subtrees don't +-- exist for k, as k is now part of them. In this function all +-- still accurate subtrees and their distances to k are +-- extracted. +-- +-- @param center The vertex serving as the starting point of the depth-first search; +-- should be the new_node + +function BalancedMinimumEvolution:setAccurateDistancesForK(center,visited,k,k_dists,leaves) + local visited = visited or {} + local distances = self.distances + + visited[center] = center + local outgoings = self.tree:outgoing(center) + for _,arc in ipairs (outgoings) do + local vertex = arc.head + if vertex ~= leaves[k] then + local distance + -- set the distance + if not distances[leaves[k]][vertex] and k_dists[center] then + distance = k_dists[center][vertex] -- use previously calculated distance + distances[leaves[k]][vertex] = distance + distances[vertex][leaves[k]] = distance + end + -- go deeper + if not visited[vertex] then + self:setAccurateDistancesForK(vertex,visited,k,k_dists,leaves) + end + end + end +end + + +-- +-- Find the best edge for the insertion of leaf #k, such that the +-- total tree length is minimized. This function uses a depth first +-- search. +-- +-- @param vertex The vertex where the depth first search is +-- started; must be a leaf +-- @param values The values needed for the recursion +-- - visited: The vertices that already have been visited +-- - tree_length: The current tree_length +-- - best_arc: The current best_arc, such that the tree +-- length is minimized +-- - min_length: The smallest tree_length found so far +function BalancedMinimumEvolution:findBestEdge(vertex, values, k_dists) + local arcs = self.tree.arcs + local vertices = self.tree.vertices + values = values or { visited = {} } + values.visited[vertex] = vertex + + local c -- the arc we came from + local unvisited_arcs = {} --unvisited arcs + --identify arcs + for _, arc in ipairs (self.tree:outgoing(vertex)) do + if not values.visited[arc.head] then + unvisited_arcs[#unvisited_arcs+1] = arc + else + c = arc.head --last visited arc + end + end + + for i, arc in ipairs (unvisited_arcs) do + local change_in_tree_length = 0 + -- set tree length to 0 for first insertion arc + if not values.tree_length then + values.tree_length = 0 + values.best_arc = arc + values.min_length = 0 + else -- compute new tree length for the case that k is inserted into this arc + local b = arc.head --current arc + local a = unvisited_arcs[i%2+1].head -- the remaining arc + local k_v = vertices[k] -- the leaf to be inserted + change_in_tree_length = 1/4 * ( ( self:distance(a,c) + + k_dists[vertex][b]) + - (self:distance(a,b) + + k_dists[vertex][c]) ) + values.tree_length = values.tree_length + change_in_tree_length + end + -- if the tree length becomes shorter, this is the new best arc + -- for the insertion of leaf k + if values.tree_length < values.min_length then + values.best_arc = arc + values.min_length = values.tree_length + end + + -- go deeper + self:findBestEdge(arc.head, values, k_dists) + + values.tree_length = values.tree_length - change_in_tree_length + end + return values.best_arc +end + +-- Calculates the total tree length +-- This is done by adding up all the edge lengths +-- +-- @return the tree length +function BalancedMinimumEvolution:calculateTreeLength() + local vertices = self.tree.vertices + local sum = 0 + + for index, v1 in ipairs(vertices) do + for i = index+1,#vertices do + local v2 = vertices[i] + local dist = self.lengths[v1][v2] + if dist then + sum = sum + dist + end + end + end + return sum +end + +-- generates edges for the final graph +-- +-- throughout the process of creating the tree, arcs have been +-- disconnected and connected, without truly creating edges. this is +-- done in this function +function BalancedMinimumEvolution:createFinalEdges() + local g = self.tree + local o_arcs = {} -- copy arcs since createEdge is going to modify the arcs array... + for _,arc in ipairs(g.arcs) do + if arc.tail.event.index < arc.head.event.index then + o_arcs[#o_arcs+1] = arc + end + end + for _,arc in ipairs(o_arcs) do + InterfaceToAlgorithms.createEdge( + self.main_algorithm, arc.tail, arc.head, + { generated_options = { + { key = "phylogenetic edge", value = tostring(self.lengths[arc.tail][arc.head]) } + }}) + end +end + + +-- Gets the distance between two nodes as specified in their options +-- or storage fields. +-- Note: this function implies that the distance from a to b is the +-- same as the distance from b to a. +-- +-- @param a,b The nodes +-- @return The distance between the two nodes + +function BalancedMinimumEvolution:distance(a, b) + if a == b then + return 0 + else + local distances = self.distances + return distances[a][b] or distances[b][a] + end +end + + +-- +-- computes the final branch lengths +-- +-- goes over all arcs and computes the final branch lengths, +-- as neither the BME nor the BNNI main_algorithm does so. +function BalancedMinimumEvolution:computeFinalLengths() + local is_leaf = self.is_leaf + local lengths = self.lengths + local g = self.tree + for _, arc in ipairs(g.arcs) do + local head = arc.head + local tail = arc.tail + local distance + local a,b,c,d + -- assert, that the length hasn't already been computed for this arc + if not lengths[head][tail] then + if not is_leaf[head] then + -- define subtrees a and b + for _, arc in ipairs (g:outgoing(head)) do + local subtree = arc.head + if subtree ~= tail then + a = a or subtree + b = subtree + end + end + end + if not is_leaf[tail] then + -- define subtrees c and d + for _, arc in ipairs (g:outgoing(tail)) do + local subtree = arc.head + if subtree ~= head then + c = c or subtree + d = subtree + end + end + end + -- compute the distance using the formula for outer or inner edges, respectively + if is_leaf[head] then + distance = 1/2 * ( self:distance(head,c) + + self:distance(head,d) + - self:distance(c,d) ) + elseif is_leaf[tail] then + distance = 1/2 * ( self:distance(tail,a) + + self:distance(tail,b) + - self:distance(a,b) ) + else --inner edge + distance = self:distance(head, tail) + -1/2 * ( self:distance(a,b) + + self:distance(c,d) ) + end + lengths[head][tail] = distance + lengths[tail][head] = distance + end + end + +end + + + +return BalancedMinimumEvolution diff --git a/graphics/pgf/base/tex/generic/graphdrawing/lua/pgf/gd/phylogenetics/BalancedNearestNeighbourInterchange.lua b/graphics/pgf/base/tex/generic/graphdrawing/lua/pgf/gd/phylogenetics/BalancedNearestNeighbourInterchange.lua new file mode 100644 index 0000000000..1980afe7ad --- /dev/null +++ b/graphics/pgf/base/tex/generic/graphdrawing/lua/pgf/gd/phylogenetics/BalancedNearestNeighbourInterchange.lua @@ -0,0 +1,372 @@ +-- Copyright 2013 by Sarah Mäusle and Till Tantau +-- +-- This file may be distributed an/or modified +-- +-- 1. under the LaTeX Project Public License and/or +-- 2. under the GNU Public License +-- +-- See the file doc/generic/pgf/licenses/LICENSE for more information + +-- @release $Header$ + + + +local BalancedNearestNeighbourInterchange = {} + + +-- Namespace +require("pgf.gd.phylogenetics").BalancedNearestNeighbourInterchange = BalancedNearestNeighbourInterchange + +-- Imports +local InterfaceToAlgorithms = require("pgf.gd.interface.InterfaceToAlgorithms") +local DistanceMatrix = require("pgf.gd.phylogenetics.DistanceMatrix") +local lib = require("pgf.gd.lib") + +-- Shorthand: +local declare = InterfaceToAlgorithms.declare + + +--- +declare { + key = "balanced nearest neighbour interchange", + algorithm = BalancedNearestNeighbourInterchange, + phase = "phylogenetic tree optimization", + phase_default = true, + + summary = [[" + The BNNI (Balanced Nearest Neighbor Interchange) is a + postprocessing algorithm for phylogenetic trees. It swaps two + distant 3-subtrees if the total tree length is reduced by doing + so, until no such swaps are left. + "]], + documentation = [[" + This algorithm is from Desper and Gascuel, \emph{Fast and + Accurate Phylogeny Reconstruction Algorithms Based on the + Minimum-Evolution Principle}, 2002. + "]] +} + + +--- +declare { + key = "no phylogenetic tree optimization", + algorithm = { run = function(self) end }, + phase = "phylogenetic tree optimization", + + summary = [[" + Switches off any phylogenetic tree optimization. + "]], +} + + + +-- creates a binary heap, implementation as an array as described in +-- the respective wikipedia article +local function new_heap() + local heap = {} + + function heap:insert(element, value) + local object = { element = element, value = value } + heap[#heap+1]= object + + local i = #heap + local parent = math.floor(i/2) + + -- sort the new object into its correct place + while heap[parent] and heap[parent].value < heap[i].value do + heap[i] = heap[parent] + heap[parent] = object + i = parent + parent = math.floor(i/2) + end + end + + -- deletes the top element from the heap + function heap:remove_top_element() + -- replace first element with last and delete the last element + local element = heap[1].element + heap[1] = heap[#heap] + heap[#heap] = nil + + local i = 1 + local left_child = 2*i + local right_child = 2*i +1 + + -- sort the new top element into its correct place by swapping it + -- against its largest child + while heap[left_child] do + local largest_child = left_child + if heap[right_child] and heap[left_child].value < heap[right_child].value then + largest_child = right_child + end + + if heap[largest_child].value > heap[i].value then + heap[largest_child], heap[i] = heap[i], heap[largest_child] + i = largest_child + left_child = 2*i + right_child = 2*i +1 + else + return element + end + end + return element + end + + return heap +end + + +-- BNNI (Balanced Nearest Neighbor Interchange) +-- [DESPER and GASCUEL: Fast and Accurate Phylogeny Reconstruction Algorithms Based on the Minimum-Evolution Principle, 2002] +-- swaps two distant-3 subtrees if the total tree length is reduced by doing so, until no such swaps are left +-- +-- step 1: precomputation of all average distances between non-intersecting subtrees (already done by BME) +-- step 2: create heap of possible swaps +-- step 3: ( current tree with subtrees a,b,c,d: a--v-- {b, w -- {c, d}} ) +-- (a): edge (v,w) is the best swap on the heap. Remove (v,c) and (w,b) +-- (b), (c), (d) : update the distance matrix +-- (e): remove the edge (v,w) from the heap; check the four edges adjacent to it for new possible swaps +-- (d): if the heap is non-empty, return to (a) + +function BalancedNearestNeighbourInterchange:run() + local g = self.tree + -- create a heap of possible swaps + local possible_swaps = new_heap() + -- go over all arcs, look for possible swaps and add them to the heap [step 2] + for _, arc in ipairs (g.arcs) do + self:getBestSwap(arc, possible_swaps) + end + + -- achieve best swap and update the distance matrix, until there is + -- no more swap to perform + + while #possible_swaps > 0 do + -- get the best swap and delete it from the heap + local swap = possible_swaps:remove_top_element() --[part of step 3 (a)] + + -- Check if the indicated swap is still possible. Another swap may + -- have interfered. + if g:arc(swap.v, swap.subtree1) and g:arc(swap.w, swap.subtree2) and g:arc(swap.v, swap.w) and g:arc(swap.a, swap.v) and g:arc(swap.d, swap.w) then + -- insert new arcs and delete the old ones to perform the swap [part of step 3 (a)] + + -- disconnect old arcs + g:disconnect(swap.v, swap.subtree1) + g:disconnect(swap.subtree1, swap.v) + g:disconnect(swap.w, swap.subtree2) + g:disconnect(swap.subtree2, swap.w) + + -- connect new arcs + g:connect(swap.v, swap.subtree2) + g:connect(swap.subtree2, swap.v) + g:connect(swap.w, swap.subtree1) + g:connect(swap.subtree1, swap.w) + + --update distance matrix + self:updateBNNI(swap) + + -- update heap: check neighboring arcs for new possible swaps + -- [step 3 (e)] + self:getBestSwap(g:arc(swap.a,swap.v), possible_swaps) + self:getBestSwap(g:arc(swap.subtree2, swap.v), possible_swaps) + self:getBestSwap(g:arc(swap.d,swap.w), possible_swaps) + self:getBestSwap(g:arc(swap.subtree1, swap.w), possible_swaps) + end + end + +end + + +-- +-- Gets the distance between two nodes as specified in the distances +-- fields. Note: this function assumes that the distance from a to b +-- is the +-- same as the distance from b to a. +-- +-- @param a,b The nodes +-- @return The distance between the two nodes +function BalancedNearestNeighbourInterchange:distance(a, b) + if a == b then + return 0 + else + local distances = self.distances + return distances[a][b] or distances[b][a] + end +end + +-- updates the distance matrix after a swap has been performed [step3(b),(c),(d)] +-- +-- @param swap A table containing the information on the performed swap +-- subtree1, subtree2: the two subtrees, which +-- were swapped +-- a, d: The other two subtrees bordering the +-- swapping edge +-- v, w : the two nodes connecting the swapping edge + +function BalancedNearestNeighbourInterchange:updateBNNI(swap) + local g = self.tree + local b = swap.subtree1 + local c = swap.subtree2 + local a = swap.a + local d = swap.d + local v = swap.v + local w = swap.w + local distances = self.distances + + -- updates the distances in one of the four subtrees adjacent to the + -- swapping edge + function update_BNNI_subtree(swap, values) + local g = self.tree + local b = swap.farther + local c = swap.nearer + local a = swap.subtree + local v = swap.v + local d = swap.same + local w = swap.w + + if not values then + values = { + visited = {[v] = v}, + possible_ys = {v}, + x = a, + y = v + } + -- if we're looking at subtrees in one of the swapped subtrees, + -- then need the old root (w) for the calculations + if swap.swapped_branch then values.possible_ys = {w} end + end + local visited = values.visited + local x = values.x + local y = values.y + local ys = values.possible_ys + local l = 0 -- number of edges between y and v + + local dist_x_b = self:distance(x,b) + local dist_x_c = self:distance(x,c) + visited[x] = x --mark current x as visited + + -- loop over possible y's: + for _, y in ipairs (ys) do + -- update distance [step 3(b)] + local distance = self:distance(x,y) - 2^(-l-2)*dist_x_b + 2^(-l-2)*dist_x_c + + if y == w then y = v end -- the old distance w,x was used for the new distance calculation, but it needs to be + -- saved under its appropriate new name according to its new root. this case only arises when looking at x's + -- in one of the swapped subtrees (b or c) + + distances[x][y] = distance + distances[y][x] = distance + l = l+1 -- length + 1, as the next y will be further away from v + end + + -- update the distance between x and w (root of subtree c and d) + -- [step 3(c)] + local distance = 1/2 * (self:distance(x,b) + self:distance(x,d)) + distances[x][w] = distance + distances[w][x] = distance + + -- go to next possible x's + table.insert(ys, x) -- when we're at the next possible x, y can also be the current x + for _,arc in ipairs (g:outgoing(x)) do + if not visited[arc.head] then + values.x = arc.head + --go deeper + update_BNNI_subtree(swap, values) + end + end + end + + -- name the nodes/subtrees in a general way that allows the use of the function update_BNNI_subtree + local update_a = {subtree = a, farther = b, nearer = c, v = v, same = d, w = w} + local update_b = {subtree = b, farther = a, nearer = d, v = w, same = c, w = v, swapped_branch = true} + local update_c = {subtree = c, farther = d, nearer = a, v = v, same = b, w = w, swapped_branch = true} + local update_d = {subtree = d, farther = c, nearer = b, v = w, same = a, w = v} + + -- update the distances within the subtrees a,b,c,d respectively + update_BNNI_subtree(update_a) + update_BNNI_subtree(update_b) + update_BNNI_subtree(update_c) + update_BNNI_subtree(update_d) + + -- update the distance between subtrees v and w [step 3 (d)]: + local distance = 1/4*( self:distance(a,b) + self:distance(a,d) + self:distance(c,b) + self:distance(c,d) ) + distances[v][w] = distance + distances[w][v] = distance +end + + + +-- finds the best swap across an arc and inserts it into the heap of +-- possible swaps +-- +-- @param arc The arc, which is to be checked for possible swaps +-- @param heap_of_swaps The heap, containing all swaps, which +-- improve the total tree length +-- +-- the following data of the swap are saved: +-- v,w = the nodes connecting the arc, across which the swap is +-- performed +-- subtree1,2 = the roots of the subtrees that are to be swapped +-- a,d = the roots of the two remaining subtrees adjacent to the arc + +function BalancedNearestNeighbourInterchange:getBestSwap(arc, heap_of_swaps) + local g = self.tree + local possible_swaps = heap_of_swaps + local v = arc.tail + local w = arc.head + local is_leaf = self.is_leaf + + -- only look at inner edges: + if not is_leaf[v] and not is_leaf[w] then + -- get the roots of the adjacent subtrees + local a, b, c, d + for _,outgoing in ipairs (g:outgoing(v)) do + local head = outgoing.head + if head ~= w then + a = a or head + b = head + end + end + + for _,outgoing in ipairs (g:outgoing(w)) do + local head = outgoing.head + if head ~= v then + c = c or head + d = head + end + end + + -- get the distances between the four subtrees + local a_b = self:distance(a,b) + local a_c = self:distance(a,c) + local a_d = self:distance(a,d) + local b_c = self:distance(b,c) + local b_d = self:distance(b,d) + local c_d = self:distance(c,d) + + -- difference in total tree length between old tree (T) and new tree (T') + -- when nodes b and c are swapped + local swap1 = 1/4*(a_b + c_d - a_c - b_d ) + + -- difference in total tree length between old tree and new tree when nodes b and d are swapped + local swap2 = 1/4*(a_b + c_d - a_d - b_c) + + -- choose the best swap that reduces the total tree length most (T-T' > 0) + if swap1 > swap2 and swap1 > 0 then + -- v,w = the nodes connecting the edge across which the swap is performed + -- subtree1 = one of the nodes to be swapped; connected to v + -- subtree2 = the other node to be swapped; connected to w + -- a = other node connected to v + -- d = other node connected to w + local swap = { v = v, w = w, subtree1 = b, subtree2 = c, a = a, d = d } + -- insert the swap into the heap + possible_swaps:insert(swap, swap1) + elseif swap2 > 0 then + local swap = { v = v, w = w, subtree1 = b, subtree2 = d, d = c, a = a } + possible_swaps:insert(swap, swap2) + end + end +end + + + +return BalancedNearestNeighbourInterchange diff --git a/graphics/pgf/base/tex/generic/graphdrawing/lua/pgf/gd/phylogenetics/DistanceMatrix.lua b/graphics/pgf/base/tex/generic/graphdrawing/lua/pgf/gd/phylogenetics/DistanceMatrix.lua new file mode 100644 index 0000000000..a65a83da17 --- /dev/null +++ b/graphics/pgf/base/tex/generic/graphdrawing/lua/pgf/gd/phylogenetics/DistanceMatrix.lua @@ -0,0 +1,451 @@ +-- Copyright 2013 by Till Tantau +-- +-- This file may be distributed an/or modified +-- +-- 1. under the LaTeX Project Public License and/or +-- 2. under the GNU Public License +-- +-- See the file doc/generic/pgf/licenses/LICENSE for more information + +-- @release $Header$ + + + + +local DistanceMatrix = {} + + +-- Imports +local InterfaceToAlgorithms = require("pgf.gd.interface.InterfaceToAlgorithms") +local declare = InterfaceToAlgorithms.declare + + +--- + +declare { + key = "distance matrix vertices", + type = "string", + + summary = [[" + A list of vertices that are used in the parsing of the + |distance matrix| key. If this key is not used at all, all + vertices of the graph will be used for the computation of a + distance matrix. + "]], + + documentation = [[" + The vertices must be separated by spaces and/or + commas. For vertices containing spaces or commas, the vertex + names may be surrounded by single or double quotes (as in + Lua). Typical examples are |a, b, c| or |"hello world", 'foo'|. + "]] +} + + + +--- + +declare { + key = "distance matrix", + type = "string", + + summary = [[" + A distance matrix specifies ``desired distances'' between + vertices in a graph. These distances are used, in particular, in + algorithms for computing phylogenetic trees. + "]], + + documentation = [[" + When this key is parsed, the key |distance matrix vertices| is + considered first. It is used to determine a list of vertices + for which a distance matrix is computed, see that key for + details. Let $n$ be the number of vertices derived from that + key. + + The string passed to the |distance matrix| key is basically + a sequence of numbers that are used to fill an $n \times n$ + matrix. This works as follows: We keep track of a \emph{current + position $p$} in the matrix, starting at the upper left corner + of the matrix. We read the numbers in the string + one by one, write it to the current position of the matrix, and + advance the current position by going right one step; if we go + past the right end of the matrix, we ``wrap around'' by going + back to the left border of the matrix, but one line down. If we + go past the bottom of the matrix, we start at the beginning once + more. + + This basic behavior can be modified in different ways. First, + when a number is followed by a semicolon instead of a comma or a + space (which are the ``usual'' ways of indicating the end of a + number), we immediately go down to the next line. Second, + instead of a number you can directly provide a \emph{position} + in the matrix and the current position will be set to this + position. Such a position information is detected by a + greater-than sign (|>|). It must be followed by + % + \begin{itemize} + \item a number or a vertex name or + \item a number or a vertex name, a comma, and another number or + vertex name or + \item a comma and a number and a vertex name. + \end{itemize} + % + Examples of the respective cases are |>1|, |>a,b|, and + |>,5|. The semantics is as follows: In all cases, if a vertex + name rather than a number is given, it is converted into a + number (namely the index of the vertex inside the matrix). Then, + in the first case, the column of the current position is set to + the given number; in the second case, the columns is set to the + first number and the column is set to the second number; and in + the third case only the row is set to the given number. (This + idea is that following the |>|-sign comes a ``coordinate pair'' + whose components are separated by a comma, but part of that pair + may be missing.) If a vertex name contains special symbols like + a space or a comma, you must surround it by single or double + quotation marks (as in Lua). + + Once the string has been parsed completely, the matrix may be + filled only partially. In this case, for each missing entry + $(x,y)$, we try to set it to the value of the entry $(y,x)$, + provided that entry is set. If neither are set, the entry is set + to $0$. + + Let us now have a look at several examples that all produce the + same matrix. The vertices are |a|, |b|, |c|. + % +\begin{codeexample}[code only, tikz syntax=false] +0, 1, 2 +1, 0, 3 +2, 3, 0 +\end{codeexample} + % +\begin{codeexample}[code only, tikz syntax=false] +0 1 2 1 0 3 2 3 0 +\end{codeexample} + % +\begin{codeexample}[code only, tikz syntax=false] +; +1; +2 3 +\end{codeexample} + % +\begin{codeexample}[code only, tikz syntax=false] +>,b 1; 2 3 +\end{codeexample} + % +\begin{codeexample}[code only, tikz syntax=false] +>b 1 2 >c 3 +\end{codeexample} + "]] +} + + +--- + +declare { + key = "distances", + type = "string", + + summary = [[" + This key is used to specify the ``desired distances'' between + a vertex and the other vertices in a graph. + "]], + + documentation = [[" + This key works similar to the |distance matrix| key, only it is + passed to a vertex instead of to a whole graph. The syntax is + the same, only the notion of different ``rows'' is not + used. Here are some examples that all have the same effect, + provided the nodes are |a|, |b|, and |c|. + % +\begin{codeexample}[code only, tikz syntax=false] +0, 1, 2 +\end{codeexample} + % +\begin{codeexample}[code only, tikz syntax=false] +0 1 2 +\end{codeexample} + % +\begin{codeexample}[code only, tikz syntax=false] +>b 1 2 +\end{codeexample} + % +\begin{codeexample}[code only, tikz syntax=false] +>c 2, >b 1 +\end{codeexample} + "]] +} + + + +local function to_index(s, indices) + if s and s ~= "" then + if s:sub(1,1) == '"' then + local _, _, m = s:find('"(.*)"') + return indices[InterfaceToAlgorithms.findVertexByName(m)] + elseif s:sub(1,1) == "'" then + local _, _, m = s:find("'(.*)'") + return indices[InterfaceToAlgorithms.findVertexByName(m)] + else + local num = tonumber(s) + if not num then + return indices[InterfaceToAlgorithms.findVertexByName(s)] + else + return num + end + end + end +end + +local function compute_indices(vertex_string, vertices) + local indices = {} + + if not vertex_string then + for i,v in ipairs(vertices) do + indices[i] = v + indices[v] = i + end + else + -- Ok, need to parse the vertex_string. Sigh. + local pos = 1 + while pos <= #vertex_string do + local start = vertex_string:sub(pos,pos) + if not start:find("[%s,]") then + local _, vertex + if start == '"' then + _, pos, vertex = vertex_string:find('"(.-)"', pos) + elseif start == "'" then + _, pos, vertex = vertex_string:find("'(.-)'", pos) + else + _, pos, vertex = vertex_string:find("([^,%s'\"]*)", pos) + end + local v = assert(InterfaceToAlgorithms.findVertexByName(vertex), "unknown vertex name '" .. vertex .. "'") + indices [#indices + 1] = v + indices [v] = #indices + end + pos = pos + 1 + end + end + + return indices +end + + +--- +-- Compute a distance matrix based on the values of a +-- |distance matrix| and a |distance matrix vertices|. +-- +-- @param matrix_string A distance matrix string +-- @param vertex_string A distance matrix vertex string +-- @param vertices An array of all vertices in the graph. +-- +-- @return A distance matrix. This matrix will contain both a +-- two-dimensional array (accessed through numbers) and also a +-- two-dimensional hash table (accessed through vertex indices). Thus, +-- you can write both |m[1][1]| and also |m[v][v]| to access the first +-- entry of this matrix, provided |v == vertices[1]|. +-- @return An index vector. This is an array of the vertices +-- identified for the |vertex_string| parameter. + +function DistanceMatrix.computeDistanceMatrix(matrix_string, vertex_string, vertices) + -- First, we create a table of the vertices we need to consider: + local indices = compute_indices(vertex_string, vertices) + + -- Second, build matrix. + local n = #indices + local m = {} + for i=1,n do + m[i] = {} + end + + local x = 1 + local y = 1 + local pos = 1 + -- Start scanning the matrix_string + while pos <= #matrix_string do + local start = matrix_string:sub(pos,pos) + if not start:find("[%s,]") then + if start == '>' then + local _, parse + _, pos, parse = matrix_string:find(">([^%s>;]*)", pos) + local a, b + if parse:find(",") then + _,_,a,b = parse:find("(.*),(.*)") + else + a = parse + end + x = to_index(a, indices) or x + y = to_index(b, indices) or y + elseif start == ';' then + x = 1 + y = y + 1 + elseif start == ',' then + x = x + 1 + else + local _, n + _, pos, n = matrix_string:find("([^,;%s>]*)", pos) + local num = assert(tonumber(n), "number expected in distance matrix") + m[x][y] = num + x = x + 1 + -- Skip everything up to first comma: + _, pos = matrix_string:find("(%s*,?)", pos+1) + end + end + pos = pos + 1 + if x > n then + x = 1 + y = y + 1 + end + if y > n then + y = 1 + end + end + + -- Fill up + for x=1,n do + for y=1,n do + if not m[x][y] then + m[x][y] = m[y][x] or 0 + end + end + end + + -- Copy to index version + for x=1,n do + local v = indices[x] + m[v] = {} + for y=1,n do + local u = indices[y] + m[v][u] = m[x][y] + end + end + + return m, indices +end + + + + +--- +-- Compute a distance vector. See the key |distances| for details. +-- +-- @param vector_string A distance vector string +-- @param vertex_string A distance matrix vertex string +-- @param vertices An array of all vertices in the graph. +-- +-- @return A distance vector. Like a distance matrix, this vector will +-- double indexed, once by numbers and once be vertex objects. +-- @return An index vector. This is an array of the vertices +-- identified for the |vertex_string| parameter. + +function DistanceMatrix.computeDistanceVector(vector_string, vertex_string, vertices) + -- First, we create a table of the vertices we need to consider: + local indices = compute_indices(vertex_string, vertices) + + -- Second, build matrix. + local n = #indices + local m = {} + local x = 1 + local pos = 1 + -- Start scanning the vector_string + while pos <= #vector_string do + local start = vector_string:sub(pos,pos) + if not start:find("[%s,]") then + if start == '>' then + local _, parse + _, pos, parse = vector_string:find(">([^%s>;]*)", pos) + x = to_index(parse, indices) or x + elseif start == ',' then + x = x + 1 + else + local _, n + _, pos, n = vector_string:find("([^,;%s>]*)", pos) + local num = assert(tonumber(n), "number expected in distance matrix") + m[x] = num + x = x + 1 + -- Skip everything up to first comma: + _, pos = vector_string:find("(%s*,?)", pos+1) + end + end + pos = pos + 1 + if x > n then + x = 1 + end + end + + -- Fill up + for x=1,n do + m[x] = m[x] or 0 + m[indices[x]] = m[x] + end + + return m, indices +end + + + +--- +-- Compute a distance matrix for a graph that incorporates all +-- information stored in the different options of the graph and the +-- vertices. +-- +-- @param graph A digraph object. +-- +-- @return A distance matrix for all vertices of the graph. + +function DistanceMatrix.graphDistanceMatrix(digraph) + local vertices = digraph.vertices + local n = #vertices + local m = {} + for i,v in ipairs(vertices) do + m[i] = {} + m[v] = {} + end + + local indices = {} + for i,v in ipairs(vertices) do + indices[i] = v + indices[v] = i + end + + if digraph.options['distance matrix'] then + local sub, vers = DistanceMatrix.computeDistanceMatrix( + digraph.options['distance matrix'], + digraph.options['distance matrix vertices'], + vertices + ) + + for x=1,#vers do + for y=1,#vers do + m[vers[x]][vers[y]] = sub[x][y] + end + end + end + + for i,v in ipairs(vertices) do + if v.options['distances'] then + local sub, vers = DistanceMatrix.computeDistanceVector( + v.options['distances'], + v.options['distance matrix vertices'], + vertices + ) + + for x=1,#vers do + m[vers[x]][v] = sub[x] + end + end + end + + -- Fill up number versions: + for x,vx in ipairs(vertices) do + for y,vy in ipairs(vertices) do + m[x][y] = m[vx][vy] + end + end + + return m +end + + + +return DistanceMatrix diff --git a/graphics/pgf/base/tex/generic/graphdrawing/lua/pgf/gd/phylogenetics/Maeusle2012.lua b/graphics/pgf/base/tex/generic/graphdrawing/lua/pgf/gd/phylogenetics/Maeusle2012.lua new file mode 100644 index 0000000000..a9acc48865 --- /dev/null +++ b/graphics/pgf/base/tex/generic/graphdrawing/lua/pgf/gd/phylogenetics/Maeusle2012.lua @@ -0,0 +1,778 @@ +-- Copyright 2013 by Sarah Mäusle and Till Tantau +-- +-- This file may be distributed an/or modified +-- +-- 1. under the LaTeX Project Public License and/or +-- 2. under the GNU Public License +-- +-- See the file doc/generic/pgf/licenses/LICENSE for more information + +-- @release $Header$ + + +-- Imports +local Digraph = require 'pgf.gd.model.Digraph' +local Coordinate = require 'pgf.gd.model.Coordinate' +local Path = require 'pgf.gd.model.Path' + +local layered = require 'pgf.gd.layered' + +local lib = require 'pgf.gd.lib' + +local declare = require("pgf.gd.interface.InterfaceToAlgorithms").declare + + + +-- Main class of this file: + +local Maeusle2012 = lib.class {} + +-- Namespace +require("pgf.gd.phylogenetics").Maeusle2012 = Maeusle2012 + + + + +--- +declare { + key = "rooted rectangular phylogram", + algorithm = { + base_class = Maeusle2012, + run = function (self) + local root = self:getRoot() + self:setPosForRectangularLayout(root) + end + }, + phase = "phylogenetic tree layout", + phase_default = true, + + summary = [[" + A rooted rectangular phylogram is... + "]], + documentation = [[" + ... + "]], + examples = [[" + \tikz \graph [phylogenetic tree layout, + rooted rectangular phylogram, + balanced minimum evolution, + distance matrix={ + 0 4 9 9 9 9 9 + 4 0 9 9 9 9 9 + 9 9 0 2 7 7 7 + 9 9 2 0 7 7 7 + 9 9 7 7 0 3 5 + 9 9 7 7 3 0 5 + 9 9 7 7 5 5 0}] + { a, b, c, d, e, f, g }; + "]] +} + +--- +declare { + key = "rectangular phylogram", + use = { { key = "rooted rectangular phylogram" } }, + summary = "An alias for |rooted rectangular phylogram|" +} + +--- +declare { + key = "rooted straight phylogram", + algorithm = { + base_class = Maeusle2012, + run = function (self) + local root = self:getRoot() + self:setXPos(root) + self:setYPosForStraightLayout(root) + end + }, + phase = "phylogenetic tree layout", + + summary = [[" + A rooted straight phylogram is... + "]], + documentation = [[" + ... + "]], + examples = [[" + \tikz \graph [phylogenetic tree layout, + rooted straight phylogram, + balanced minimum evolution, grow=right, + distance matrix={ + 0 4 9 9 9 9 9 + 4 0 9 9 9 9 9 + 9 9 0 2 7 7 7 + 9 9 2 0 7 7 7 + 9 9 7 7 0 3 5 + 9 9 7 7 3 0 5 + 9 9 7 7 5 5 0}] + { a, b, c, d, e, f, g }; + "]]} + +--- +declare { + key = "straight phylogram", + use = { { key = "rooted straight phylogram" } }, + summary = "An alias for |rooted straight phylogram|" +} + +--- +declare { + key = "unrooted rectangular phylogram", + algorithm = { + base_class = Maeusle2012, + run = function (self) + local root1, root2 = self:getRoot() + self:setPosForUnrootedRectangular(root2, root1) + end + }, + phase = "phylogenetic tree layout", + + summary = [[" + A unrooted rectangular phylogram is... + "]], + documentation = [[" + ... + "]], + examples = [[" + \tikz \graph [phylogenetic tree layout, + unrooted rectangular phylogram, + balanced minimum evolution, grow=right, + distance matrix={ + 0 4 9 9 9 9 9 + 4 0 9 9 9 9 9 + 9 9 0 2 7 7 7 + 9 9 2 0 7 7 7 + 9 9 7 7 0 3 5 + 9 9 7 7 3 0 5 + 9 9 7 7 5 5 0}] + { a, b, c, d, e, f, g }; + "]] +} + +--- +declare { + key = "unrooted straight phylogram", + algorithm = { + base_class = Maeusle2012, + run = function (self) + local root1, root2 = self:getRoot() + self:setPosForUnrootedStraight(root2, root1) + end + }, + phase = "phylogenetic tree layout", + + summary = [[" + A unrooted straight phylogram is... + "]], + documentation = [[" + ... + "]], + examples = [[" + \tikz \graph [phylogenetic tree layout, + unrooted straight phylogram, + balanced minimum evolution, grow=right, + distance matrix={ + 0 4 9 9 9 9 9 + 4 0 9 9 9 9 9 + 9 9 0 2 7 7 7 + 9 9 2 0 7 7 7 + 9 9 7 7 0 3 5 + 9 9 7 7 3 0 5 + 9 9 7 7 5 5 0}] + { a, b, c, d, e, f, g }; + "]] +} + + +--- +declare { + key = "evolutionary unit length", + type = "length", + initial = "1cm", + + summary = [[" + Specifies how long a ``unit'' of evolutionary time should be on + paper. For instance, if two nodes in a phylogenetic tree have an + evolutionary distance of 3 and this length is set to |1cm|, then + they will be |3cm| apart in a straight-line phylogram. + "]], + documentation = [[" + (This key used to be called |distance scaling factor|.) + "]], +} + + + +-- +-- Gets the edge length between two nodes +-- +-- @param vertex1, vertex2 The two nodes +-- +-- @return The length of the edge between the two nodes +function Maeusle2012:edgeLength(vertex1, vertex2) + return self.lengths[vertex1][vertex2] +end + + +-- Sets the x and y coordinates for all nodes, using a depth first +-- search +-- +-- @param vertex The starting point; should usually be the root +-- @param values Values needed for the recursion +-- @param vertex2 A node that will not be visited; this parameter should only be set +-- for an unrooted layout to ensure that only the half of the tree is set. +function Maeusle2012:setPosForRectangularLayout(vertex, values, vertex2) + local arcs = self.tree.arcs + local vertices = self.tree.vertices + local adjusted_bb = self.main_algorithm.adjusted_bb + + values = values or { + length = 0, -- current path length + visited = {}, -- all nodes that have already been visited + leaves = {}, -- all leaves from left to right + } + + local vertex_is_leaf = true + values.visited[vertex] = true + + local children = {} -- a table containing all children of the + -- current vertex (for the later determination of inner vertices + -- x-positions) + + + for _, arc in ipairs (self.tree:outgoing(vertex)) do + if not values.visited[arc.head] and arc.head ~= vertex2 then + -- if arc.head hasn't been visited, the current vertex cannot be a leaf + vertex_is_leaf = false + local arc_length = self:edgeLength(vertex, arc.head) + + values.length = values.length + arc_length + + -- go deeper + self:setPosForRectangularLayout(arc.head, values, vertex2) + + -- get the children of the current vertex + children[#children+1] = arc.head + + values.length = values.length - arc_length + end + end + + if vertex_is_leaf then + -- subtract layer_pre, thus the leaf itself is NOT part of the + -- edge length + vertex.pos.y = - adjusted_bb[vertex].layer_pre + + values.leaves[#values.leaves+1] = vertex + + -- x coordinate: + -- the x coordinates of the leaves are the first to be set; the + -- first leave stays at x = 0, the x coordinates for the other + -- leaves is computed with help of the ideal_sibling_distance + -- function + if #values.leaves > 1 then + local left_sibling = values.leaves[#values.leaves-1] + local ideal_distance = layered.ideal_sibling_distance(adjusted_bb, self.tree, vertex, left_sibling ) + vertex.pos.x = left_sibling.pos.x + ideal_distance + end + + else -- the vertex is an inner node + -- the x position of an inner vertex is at the center of its children. + + -- determine the outer children + local left_child = children[1] + local right_child = left_child + for _, child in ipairs(children) do + if child.pos.x < left_child.pos.x then left_child = child end + if child.pos.x > right_child.pos.x then right_child = child end + end + + -- position between child with highest and child with lowest x-value, + -- if number of children is even + local index_of_middle_child = math.ceil(#children/2) + local even = #children/2 == index_of_middle_child + + if even then + vertex.pos.x = (left_child.pos.x + right_child.pos.x) / 2 + index_of_middle_child = 0 + else -- if number of children is odd, position above the middle child + vertex.pos.x = children[index_of_middle_child].pos.x + table.remove(children, index_of_middle_child) -- don't bend the edge to this node, as it it above it anyway + end + end + + -- set the node's y-coordinate, using the calculated length + -- and a scaling factor + vertex.pos.y = vertex.pos.y + (values.length * self.tree.options['evolutionary unit length']) + + -- if this is the second subtree to be set of an unrooted tree, have + -- it grow in the other direction + if values.second_subtree then + vertex.pos.y = -vertex.pos.y + end + + -- bend the edges for the rectangular layout + for i,child in ipairs(children) do + self:bendEdge90Degree(child, vertex) + end + + return values +end + + +-- Sets only the x-positions of all nodes using a depth-first search. +-- This is necessary for straight-edge layouts. +-- +-- @param vertex The starting point of the depth-first search; should usually be the root +-- @param values Values needed for the recursion +-- @param vertex2 A node that will not be visited; this parameter should only be set +-- for an unrooted layout to ensure that only the half of the tree is set. +function Maeusle2012:setXPos(vertex, values, vertex2) + local arcs = self.tree.arcs + local vertices = self.tree.vertices + if not values then + values = { + visited = {}, -- all nodes that have already been visited + leaves = {}, -- all leaves from left to right + } + end + + local vertex_is_leaf = true + values.visited[vertex] = true + local children = {} -- a table containing all children of the current vertex (for the later determination of inner vertices x-positions) + + for _, arc in ipairs (self.tree:outgoing(vertex)) do + if not values.visited[arc.head] and arc.head ~= vertex2 then + -- if arc.head hasn't been visited, the current vertex cannot be a leaf + vertex_is_leaf = false + + -- go deeper + self:setXPos(arc.head, values, vertex2) + + -- get the children of the current vertex + table.insert(children, arc.head) + end + end + + -- set the x-position of a leaf + if vertex_is_leaf then + + table.insert(values.leaves, vertex) + + if #values.leaves > 1 then + local left_sibling = values.leaves[#values.leaves-1] + local ideal_distance = layered.ideal_sibling_distance(self.main_algorithm.adjusted_bb, self.tree, vertex, left_sibling ) + vertex.pos.x = left_sibling.pos.x + ideal_distance + end + + -- set x position of an inner node, which is at the center of its + -- children + else + -- determine the outer children + local left_child = children[1] + local right_child = left_child + for _, child in ipairs(children) do + if child.pos.x < left_child.pos.x then left_child = child end + if child.pos.x > right_child.pos.x then right_child = child end + end + + -- position between child with highest and child with lowest x-value, + -- if number of children is even + local index_of_middle_child = math.ceil(#children/2) + local even = #children/2 == index_of_middle_child + + if even then + vertex.pos.x = (left_child.pos.x + right_child.pos.x) / 2 + else -- if number of children is odd, position above the middle child + vertex.pos.x = children[index_of_middle_child].pos.x + end + end + return values +end + + +-- +-- Sets only the y-positions of all nodes using a depth-first search. +-- This is needed for a straight-edge layout, as the x-positions have +-- to bet first so that the y-coordinates can be calculated correctly +-- here. +-- +-- @param vertex1 The starting point of the depth-first search +-- @param values Values needed for the recursion +-- @param vertex2 For unrooted layout only: The root of the second subtree. +-- This node and all its children will not be visited. +function Maeusle2012:setYPosForStraightLayout(vertex, values, vertex2) + local arcs = self.tree.arcs + local vertices = self.tree.vertices + local adjusted_bb = self.main_algorithm.adjusted_bb + + values = values or { + length = 0, -- current path length + visited = {}, -- all nodes that have already been visited + leaves = {}, -- all leaves from left to right + } + + local vertex_is_leaf = true + values.visited[vertex] = true + local children = {} -- a table containing all children of the current vertex (for the later determination of inner vertices x-positions) + + for _, arc in ipairs (self.tree:outgoing(vertex)) do + if not values.visited[arc.head] and arc.head ~= vertex2 then + -- if arc.head hasn't been visited, the current vertex cannot be a leaf + vertex_is_leaf = false + + -- calculate the arc length with the help of the Pythagorean + -- theorem + local a + local l = self:edgeLength(vertex, arc.head) * self.tree.options['evolutionary unit length'] + local b = math.abs(vertex.pos.x - arc.head.pos.x) + if b > l then + a = 0 + else + a = math.sqrt(l^2-b^2) + end + local arc_length = a + + + values.length = values.length + arc_length + + -- go deeper + self:setYPosForStraightLayout(arc.head, values, vertex2) + + -- get the children of the current vertex + table.insert(children, arc.head) + + values.length = values.length - arc_length + end + end + + if vertex_is_leaf then + -- subtract layer_pre, thus the leaf itself is NOT part of the + -- edge length + vertex.pos.y = - adjusted_bb[vertex].layer_pre + + table.insert(values.leaves, vertex) + end + + -- set the node's y-coordinate, using the calculated length + vertex.pos.y = vertex.pos.y + values.length + + -- if this is the second subtree to be set of an unrooted tree, have + -- it grow in the other direction + if values.second_subtree then vertex.pos.y = -vertex.pos.y end +end + +-- +-- Correct the x-positions in the unrooted layout for a more aesthetic result +-- +-- If the roots of the two subtrees have different x-positions, this is corrected +-- by shifting the x-positions of all nodes in one subtree by that difference. +-- +-- @param vertex1 The root of the first subtree +-- @param vertex2 The root of the second subtree. +function Maeusle2012:correctXPos(vertex1, vertex2, straight) + + -- correct the x-positions + -- + -- @param vertex Starting point of the depth-first search + -- @param values Values needed for the recursion + -- @param vertex2 The root of the subtree that will not be visited + local function x_correction(vertex, values, vertex2) + values.visited[vertex] = true + local children = {} + + for _, arc in ipairs (self.tree:outgoing(vertex)) do + if not values.visited[arc.head] and arc.head ~= vertex2 then + + table.insert(children, arc.head) + x_correction(arc.head, values, vertex2) + end + end + + vertex.pos.x = vertex.pos.x + values.diff + if not straight then + for i,child in ipairs(children) do + self:bendEdge90Degree(child, vertex) + end + end + + return values + end + + -- compute the difference of the x-positions of the two subtrees' + -- roots + local diff = vertex1.pos.x - vertex2.pos.x + local values = { visited = {} } + if diff < 0 then + values.diff = - diff + x_correction(vertex1, values, vertex2) + elseif diff > 0 then + values.diff = diff + x_correction(vertex2, values, vertex1) + end +end + + +-- +-- Sets the x- and y-positions of the vertices in an unrooted layout +-- +-- This is done using the function for setting the positions for a rooted layout: +-- Two neighboring vertices are chosen as roots; one half of the tree +-- is drawn in one direction, the other half 180° to the other +-- direction. +-- +-- @param vertex1, vertex2: The vertices functioning as roots +function Maeusle2012:setPosForUnrootedRectangular(vertex1, vertex2) + -- set positions for first half of the tree... + self:setPosForRectangularLayout(vertex2,false,vertex1) + local vals={ + length = self:edgeLength(vertex1, vertex2), -- the length between the two roots + visited={}, + leaves={}, + path={}, + second_subtree = true + } + -- ... and for the second half. + self:setPosForRectangularLayout(vertex1,vals,vertex2) + -- if the two roots have different x-values, correct the x-positions for nicer layout + self:correctXPos(vertex1, vertex2, false) +end + + +-- +-- Sets the x- and y-positions of the vertices in an unrooted straight layout +-- +-- This is done using the function for setting the positions for a rooted straight layout: +-- Two neighboring vertices are chosen as roots; one half of the tree +-- is drawn in one direction, the other half 180° to the other +-- direction. +-- +-- @param vertex1, vertex2: The vertices functioning as roots +function Maeusle2012:setPosForUnrootedStraight(vertex1, vertex2) + -- first set the x-positions of the two subtrees... + local vals = {visited = {}, leaves = {} } + self:setXPos(vertex2, vals, vertex1) + self:setXPos(vertex1, vals, vertex2) + + -- ... and then the y-positions + self:setYPosForStraightLayout(vertex2, false, vertex1) + local vals={ + length = self:edgeLength(vertex1, vertex2) * self.tree.options['evolutionary unit length'], + visited={}, + leaves={}, + path={}, + second_subtree = true + } + self:setYPosForStraightLayout(vertex1, vals, vertex2) + + -- if the two roots have different x-values, correct the x-positions for nicer layout + -- as the length between the roots of the two subtrees is set to the calculated value, + -- this step is mandatory for the unrooted, straight layout + self:correctXPos(vertex1, vertex2, true) +end + + + +-- Bends the arc between two nodes by 90 degree by updating the arc's +-- path +-- +-- @param head The head of the arc +-- @param tail The tail of the arc +function Maeusle2012:bendEdge90Degree(head, tail) + local arc = self.tree:arc(tail,head) + local syntactic_tail = arc:syntacticTailAndHead() + arc:setPolylinePath { Coordinate.new(head.pos.x, tail.pos.y) } +end + + + +-- Finds the longest path in a graph +-- +-- @ return A table containing the path (an array of nodes) and the +-- path length +function Maeusle2012:findLongestPath() + local starting_point = self.tree.vertices[1] -- begin at any vertex + -- get the path lengths from the starting point to all leaves: + local paths_to_leaves = self:getPathLengthsToLeaves(starting_point) + local path_lengths = paths_to_leaves.path_lengths + local paths = paths_to_leaves.paths + + -- looks for the longest path and identifies its end-point + local function find_head_of_longest_path(path_lengths, paths) + local longest_path + local node + -- to make sure that the same path is chosen every time, we go over all vertices with "ipairs"; if we would go over path_lengths directly, we could only use "pairs" + for _, vertex in ipairs(self.tree.vertices) do + local path_length = path_lengths[vertex] + if path_length then + -- choose longest path. if two paths have the same length, take the path with more nodes + if not longest_path or path_length > longest_path or (path_length == longest_path and #paths[vertex]>#paths[node]) then + longest_path = path_length + node = vertex + end + end + end + return node + end + + -- find the longest path leading away from the starting point and identify + -- the leaf it leads to. Use that leaf as the tail for the next path + -- search + local tail = find_head_of_longest_path(path_lengths, paths) + paths_to_leaves = self:getPathLengthsToLeaves(tail) -- gets new path information + -- paths_to leaves now has all paths starting at vertex "tail"; one of these paths is the + -- longest (globally) + path_lengths = paths_to_leaves.path_lengths + paths = paths_to_leaves.paths + local head = find_head_of_longest_path(path_lengths, paths) + + local path_information = + { path = paths_to_leaves.paths[head], -- longest path + length = path_lengths[head] } -- length of that path + + return path_information +end + + +-- a depth first search for getting all path lengths from a +-- starting point to all leaves +-- +-- @param vertex The vertex where the search is to start +-- @param values Table of values needed for the recursive computation +-- +-- @return A table containing: +-- a table of the leaves with corresponding path lengths +-- and a table containing the path to each leaf (an array of +-- nodes) +function Maeusle2012:getPathLengthsToLeaves(vertex, values) + local arcs = self.tree.arcs + local vertices = self.tree.vertices + if not values then + values = { + paths = {}, -- all paths we've found so far + path_lengths = {}, -- all path lengths that have so far been computed + length = 0, -- current path length + visited = {}, -- all nodes that have already been visited + path = {}, -- the current path we're on + leaves = {} -- all leaves from left to right + } + table.insert(values.path,vertex) + end + + local vertex_is_leaf = true + values.visited[vertex] = true + + for _, arc in ipairs (self.tree:outgoing(vertex)) do + if not values.visited[arc.head] then + -- the current vertex is not a leaf! note: if the starting vertex is a leaf, vertex_is_leaf + -- will be set to 'false' for it anyway. as we're not interested in the distance + -- of the starting vertex to itself, this is fine. + vertex_is_leaf = false + local arc_length = self.lengths[vertex][arc.head] + values.length = values.length + arc_length + + -- add arc.head to path... + table.insert(values.path,arc.head) + + -- ... and go down that path + self:getPathLengthsToLeaves(arc.head, values) + + -- remove arc.head again to go a different path + table.remove(values.path) + values.length = values.length - arc_length + end + end + + if vertex_is_leaf then -- we store the information gained on the path to this leaf + values.path_lengths[vertex] = values.length + values.paths[vertex] = {} + table.insert(values.leaves, vertex) + for i,k in pairs(values.path) do + values.paths[vertex][i] = k + end + end + -- the path_lengths and the paths are stored in one table and + -- returned together + local path_information = + { path_lengths = values.path_lengths, + paths = values.paths, + leaves = values.leaves } + return path_information +end + + +-- Gets the root of a tree +-- checks whether a tree is already rooted, if not, computeCenterOfPath() is +-- called, which defines a node in the center of the graph as the root +-- +-- @return The root +function Maeusle2012:getRoot() + -- check whether a root exists (vertex with degree 2) + local root = lib.find (self.tree.vertices, function(v) return #self.tree:outgoing(v) == 2 end) + if root then + return root, self.tree:outgoing(root)[1].head + else + return self:computeCenterOfPath() + end +end + + +-- +-- @return The newly computed root and its nearest neighbor +function Maeusle2012:computeCenterOfPath() + local longest_path = self:findLongestPath() + local path = longest_path.path + local root, neighbor_of_root + + local length = 0 --length between first vertex on the path and the current vertex we're looking at + for i = 1, #path-1 do + local node1 = path[i] + local node2 = path[i+1] + local node3 = path[i+2] + + local dist_node_1_2, dist_node_2_3 --distances between node1 and node2, and node2 and node3 + dist_node_1_2 = self:edgeLength(node1, node2) + if node3 then dist_node_2_3 = self:edgeLength(node2, node3) end + length = length + dist_node_1_2 -- length between first vertex on the path and current node2 + + if length == longest_path.length/2 then + root = node2 -- if there is a node exactly at the half of the path, use this node as root + + -- and find nearest neighbor of the root + if node3 == nil or dist_node_1_2 < dist_node_2_3 then -- neu 3.8 + neighbor_of_root = node1 + else + neighbor_of_root = node3 + end + break + + elseif length > longest_path.length/2 then + -- else find node closest to the center of the path and use it as the root; + local node2_length = math.abs(longest_path.length/2 - length) + local node1_length = math.abs(longest_path.length/2 - (length - dist_node_1_2)) + if node2_length < node1_length then + root = node2 + neighbor_of_root = node1 + -- if node3 is closer to node2 than node1 is, use node3 as neighbor! + if node3 and dist_node_2_3 < dist_node_1_2 then neighbor_of_root = node3 end + else + root = node1 + neighbor_of_root = node2 + --check if node i-1 is closer to node1 + local dist_node_0_1 + if i>1 then + node0 = path[i-1] + dist_node_0_1 = self:edgeLength(node0, node1) + if dist_node_0_1 < dist_node_1_2 then neighbor_of_root = node0 end + end + end + break + end + end + + return root, neighbor_of_root +end + + +return Maeusle2012 diff --git a/graphics/pgf/base/tex/generic/graphdrawing/lua/pgf/gd/phylogenetics/PhylogeneticTree.lua b/graphics/pgf/base/tex/generic/graphdrawing/lua/pgf/gd/phylogenetics/PhylogeneticTree.lua new file mode 100644 index 0000000000..242ccf183f --- /dev/null +++ b/graphics/pgf/base/tex/generic/graphdrawing/lua/pgf/gd/phylogenetics/PhylogeneticTree.lua @@ -0,0 +1,91 @@ +-- Copyright 2013 by Sarah Mäusle and Till Tantau +-- +-- This file may be distributed an/or modified +-- +-- 1. under the LaTeX Project Public License and/or +-- 2. under the GNU Public License +-- +-- See the file doc/generic/pgf/licenses/LICENSE for more information + +-- @release $Header$ + + +local PhylogeneticTree = {} + + +-- Namespace +require("pgf.gd.phylogenetics").PhylogeneticTree = PhylogeneticTree + +-- Imports +local InterfaceToAlgorithms = require "pgf.gd.interface.InterfaceToAlgorithms" +local Storage = require "pgf.gd.lib.Storage" +local Direct = require "pgf.gd.lib.Direct" + +-- Shorthand: +local declare = InterfaceToAlgorithms.declare + + +--- +declare { + key = "phylogenetic tree layout", + algorithm = PhylogeneticTree, + + postconditions = { + upward_oriented = true + }, + + summary = [[" + Layout for drawing phylogenetic trees. + "]], + documentation = [[" + ... + "]], + examples = [[" + \tikz \graph [phylogenetic tree layout, upgma, + distance matrix={ + 0 4 9 9 9 9 9 + 4 0 9 9 9 9 9 + 9 9 0 2 7 7 7 + 9 9 2 0 7 7 7 + 9 9 7 7 0 3 5 + 9 9 7 7 3 0 5 + 9 9 7 7 5 5 0}] + { a, b, c, d, e, f, g }; + "]] +} + + +-- Computes a phylogenetic tree and/or visualizes it +-- - computes a phylogenetic tree according to what the "phylogenetic +-- algorithm" key is set to +-- - invokes a graph drawing algorithm according to what the +-- "phylogenetic layout" key is set to +function PhylogeneticTree:run() + + local options = self.digraph.options + + -- Two storages for some information computed by the phylogenetic + -- tree generation algorithm + local lengths = Storage.newTableStorage() + + -- First, compute the phylogenetic tree + local tree = options.algorithm_phases['phylogenetic tree generation'].new { + main_algorithm = self, + lengths = lengths + }:run() + + tree = Direct.ugraphFromDigraph(tree) + + -- Second, layout the tree + local layout_class = options.algorithm_phases['phylogenetic tree layout'] + layout_class.new { + main_algorithm = self, + distances = distances, + lengths = lengths, + tree = tree + }:run() + + tree:sync() +end + +return PhylogeneticTree diff --git a/graphics/pgf/base/tex/generic/graphdrawing/lua/pgf/gd/phylogenetics/SokalMichener1958.lua b/graphics/pgf/base/tex/generic/graphdrawing/lua/pgf/gd/phylogenetics/SokalMichener1958.lua new file mode 100644 index 0000000000..6a665b6732 --- /dev/null +++ b/graphics/pgf/base/tex/generic/graphdrawing/lua/pgf/gd/phylogenetics/SokalMichener1958.lua @@ -0,0 +1,261 @@ +-- Copyright 2013 by Sarah Mäusle and Till Tantau +-- +-- This file may be distributed an/or modified +-- +-- 1. under the LaTeX Project Public License and/or +-- 2. under the GNU Public License +-- +-- See the file doc/generic/pgf/licenses/LICENSE for more information + +-- @release $Header$ + + + + +local SokalMichener1958 = {} + + +-- Namespace +require("pgf.gd.phylogenetics").SokalMichener1958 = SokalMichener1958 + +-- Imports +local InterfaceToAlgorithms = require("pgf.gd.interface.InterfaceToAlgorithms") +local DistanceMatrix = require("pgf.gd.phylogenetics.DistanceMatrix") +local lib = require("pgf.gd.lib") +local Storage = require("pgf.gd.lib.Storage") +local Digraph = require("pgf.gd.model.Digraph") + +-- Shorthand: +local declare = InterfaceToAlgorithms.declare + + +--- +declare { + key = "unweighted pair group method using arithmetic averages", + algorithm = SokalMichener1958, + phase = "phylogenetic tree generation", + + summary = [[" + The UPGMA (Unweighted Pair Group Method using arithmetic + Averages) algorithm of Sokal and Michener, 1958. It generates a + graph on the basis of such a distance matrix by generating nodes + and computing the edge lengths. + "]], + documentation = [[" + This algorithm uses a distance matrix, ideally an ultrametric + one, to compute the graph. + "]], + examples = [[" + \tikz \graph [phylogenetic tree layout, sibling distance=0pt, sibling sep=2pt, + unweighted pair group method using arithmetic averages, + distance matrix={ + 0 4 9 9 9 9 9 + 4 0 9 9 9 9 9 + 9 9 0 2 7 7 7 + 9 9 2 0 7 7 7 + 9 9 7 7 0 3 5 + 9 9 7 7 3 0 5 + 9 9 7 7 5 5 0}] + { a, b, c, d, e, f, g }; + "]] +} + + +--- +declare { + key = "upgma", + use = { { key = "unweighted pair group method using arithmetic averages" } }, + summary = "An shorthand for |unweighted pair group method using arithmetic averages|" +} + + + + +-- +-- The run function of the upgma algorithm. +-- +-- You must setup the following fields: The |main_algorithm| must +-- store the main algorithm object (for phase |main|). The |distances| +-- field must be a |Storage| object that will get filled with the +-- distances computed by this algorithm. The |lengths| field must also +-- be a |Storage| for the computed distances. +-- + +function SokalMichener1958:run() + self.distances = Storage.newTableStorage() + + self.tree = Digraph.new(self.main_algorithm.digraph) + + -- store the phylogenetic tree object, containing all user-specified + -- graph information + self:runUPGMA() + self:createFinalEdges() + + return self.tree +end + + + +-- UPGMA (Unweighted Pair Group Method using arithmetic Averages) algorithm +-- (Sokal and Michener, 1958) +-- +-- this function generates a graph on the basis of such a distance +-- matrix by generating nodes and computing the edge lengths; the x- +-- and y-positions of the nodes must be set separately +-- +-- requirement: a distance matrix, ideally an ultrametric +function SokalMichener1958:runUPGMA() + local matrix = DistanceMatrix.graphDistanceMatrix(self.tree) + + local g = self.tree + local clusters = {} + + -- create the clusters + for _,v in ipairs(g.vertices) do + clusters[#clusters+1] = self:newCluster(v) + end + + -- Initialize the distances of these clusters: + for _,cx in ipairs(clusters) do + for _,cy in ipairs(clusters) do + cx.distances[cy] = matrix[cx.root][cy.root] + end + end + + -- search for clusters with smallest distance and merge them + while #clusters > 1 do + local minimum_distance = math.huge + local min_cluster1 + local min_cluster2 + for i, cluster in ipairs (clusters) do + for j = i+1,#clusters do + local cluster2 = clusters[j] + local cluster_distance = self:getClusterDistance(cluster, cluster2) + if cluster_distance < minimum_distance then + minimum_distance, min_cluster1, min_cluster2 = cluster_distance, i, j + end + end + end + self:mergeClusters(clusters, min_cluster1, min_cluster2, minimum_distance) + end +end + + +-- a new cluster is created +-- +-- @param vertex The vertex the cluster is initialized with +-- +-- @return The new cluster +function SokalMichener1958:newCluster(vertex) + return { + root = vertex, -- the root of the cluster + size = 1, -- the number of vertices in the cluster, + distances = {}, -- cached cluster distances to all other clusters + cluster_height = 0 -- this value is equivalent to half the distance of the last two clusters + -- that have been merged to form the current cluster; + -- necessary for determining the distances of newly generated nodes to their children. + } +end + + +-- gets the distance between two clusters +-- +-- @param cluster1, cluster2 The two clusters +-- +-- @return the distance between the clusters +function SokalMichener1958:getClusterDistance(c,d) + return c.distances[d] or d.distances[c] or 0 +end + + +-- merges two clusters by doing the following: +-- - deletes cluster2 from the clusters table +-- - adds all vertices from cluster2 to the vertices table of cluster1 +-- - updates the distances of the new cluster to all remaining clusters +-- - generates a new node, as the new root of the cluster +-- - computes the distance of the new node to the former roots (for +-- later computation of the y-positions) +-- - generates edges, connecting the new node to the former roots +-- - updates the cluster height +-- +-- @param clusters The array of clusters +-- @param index_of_first_cluster The index of the first cluster +-- @param index_of_second_cluster The index of the second cluster +-- @param distance The distance between the two clusters + +function SokalMichener1958:mergeClusters(clusters, index_of_first_cluster, index_of_second_cluster, distance) + + local g = self.tree + local cluster1 = clusters[index_of_first_cluster] + local cluster2 = clusters[index_of_second_cluster] + + --update cluster distances + for i,cluster in ipairs (clusters) do + if cluster ~= cluster1 and cluster ~= cluster2 then + local dist1 = self:getClusterDistance (cluster1, cluster) + local dist2 = self:getClusterDistance (cluster2, cluster) + local dist = (dist1*cluster1.size + dist2*cluster2.size)/ (cluster1.size+cluster2.size) + cluster1.distances[cluster] = dist + cluster.distances[cluster1] = dist + end + end + + -- delete cluster2 + table.remove(clusters, index_of_second_cluster) + + --add node and connect last vertex of each cluster with new node + local new_node = InterfaceToAlgorithms.createVertex( + self.main_algorithm, + { + name = "UPGMA-node ".. #self.tree.vertices+1, + generated_options = { { key = "phylogenetic inner node" } }, + } + ) + g:add{new_node} + -- the distance of the new node ( = the new root of the cluster) to its children (= the former roots) is + -- equivalent to half the distance between the two former clusters + -- minus the respective cluster height + local distance1 = distance/2-cluster1.cluster_height + self.distances[new_node][cluster1.root] = distance1 + local distance2 = distance/2-cluster2.cluster_height + self.distances[new_node][cluster2.root] = distance2 + + -- these distances are also the final edge lengths, thus: + self.lengths[new_node][cluster1.root] = distance1 + self.lengths[cluster1.root][new_node] = distance1 + + self.lengths[new_node][cluster2.root] = distance2 + self.lengths[cluster2.root][new_node] = distance2 + + g:connect(new_node, cluster1.root) + g:connect(new_node, cluster2.root) + + cluster1.root = new_node + cluster1.size = cluster1.size + cluster2.size + cluster1.cluster_height = distance/2 -- set new height of the cluster +end + + + +-- generates edges for the final graph +-- +-- throughout the process of creating the tree, arcs have been +-- disconnected and connected, without truly creating edges. this is +-- done in this function +function SokalMichener1958:createFinalEdges() + local g = self.tree + local o_arcs = {} -- copy arcs since createEdge is going to modify the arcs array... + for _,arc in ipairs(g.arcs) do + o_arcs[#o_arcs+1] = arc + end + for _,arc in ipairs(o_arcs) do + InterfaceToAlgorithms.createEdge( + self.main_algorithm, arc.tail, arc.head, + { generated_options = { + { key = "phylogenetic edge", value = tostring(self.lengths[arc.tail][arc.head]) } + }}) + end +end + + +return SokalMichener1958 diff --git a/graphics/pgf/base/tex/generic/graphdrawing/lua/pgf/gd/phylogenetics/library.lua b/graphics/pgf/base/tex/generic/graphdrawing/lua/pgf/gd/phylogenetics/library.lua new file mode 100644 index 0000000000..5ab821c4a8 --- /dev/null +++ b/graphics/pgf/base/tex/generic/graphdrawing/lua/pgf/gd/phylogenetics/library.lua @@ -0,0 +1,35 @@ +-- Copyright 2012 by Till Tantau +-- +-- This file may be distributed an/or modified +-- +-- 1. under the LaTeX Project Public License and/or +-- 2. under the GNU Public License +-- +-- See the file doc/generic/pgf/licenses/LICENSE for more information + +-- @release $Header$ + + + +--- +-- A phylogenetic tree (or network) depicts the evolutionary history +-- of species or, more generally, so called taxa. The present library +-- includes a number of algorithms for drawing phylogenetic trees. +-- +-- @library + +local phylogenetics -- Library name + +-- Main layout: +require "pgf.gd.phylogenetics.PhylogeneticTree" + +-- Phylogenetic tree drawing: +require "pgf.gd.phylogenetics.Maeusle2012" + +-- Phylogenetic tree generation: +require "pgf.gd.phylogenetics.SokalMichener1958" +require "pgf.gd.phylogenetics.BalancedMinimumEvolution" +require "pgf.gd.phylogenetics.BalancedNearestNeighbourInterchange" +require "pgf.gd.phylogenetics.AuthorDefinedPhylogeny" + + diff --git a/graphics/pgf/base/tex/generic/graphdrawing/lua/pgf/gd/planar.lua b/graphics/pgf/base/tex/generic/graphdrawing/lua/pgf/gd/planar.lua new file mode 100644 index 0000000000..5d80de77cd --- /dev/null +++ b/graphics/pgf/base/tex/generic/graphdrawing/lua/pgf/gd/planar.lua @@ -0,0 +1,6 @@ +require "pgf" +require "pgf.gd" + +pgf.gd.planar = {} + +return pgf.gd.planar diff --git a/graphics/pgf/base/tex/generic/graphdrawing/lua/pgf/gd/planar/BoyerMyrvold2004.lua b/graphics/pgf/base/tex/generic/graphdrawing/lua/pgf/gd/planar/BoyerMyrvold2004.lua new file mode 100644 index 0000000000..f587861d79 --- /dev/null +++ b/graphics/pgf/base/tex/generic/graphdrawing/lua/pgf/gd/planar/BoyerMyrvold2004.lua @@ -0,0 +1,678 @@ + +--[[ +---Data structures--- + +Vertices from the original ugraph are referred to as input vertices. +The tables that contain vertex data relevant to the algorithm +are referred to as vertices. +A vertex table may have the following keys: + +-sign +1 or -1, indicates whether this and all in the depth-first search +following vertices must be considered flipped +(i. e. adjacency lists reversed) in respect to the dfs parent + +-childlist +A linked list containing all dfs children of the vertex whose virtual roots +have not yet been merged into the vertex, sorted by lowpoint + +-adjlistlinks +A table with two fields with keys 0 and 1, containing the two half edges +of the vertex which lie on the external face of the graph +(if the vertex lies on the external face). +The half edge with key 0 lies in the 0-direction of the other half edge +and vice-versa +The two fields may hold the same half edge, if the vertex has degree one + +-pertinentroots +A linked list containing all virtual roots of this vertex that are +pertinent during the current step + +-inputvertex +The input vertex that corresponds to the vertex + +-dfi +The depth-first search index (number of the step in the dfs at which +the vertex was discovered) + +-dfsparent +The depth-first search parent (vertex from which the vertex was discovered +first in the dfs) + +-leastancestor +Dfi of the vertex with lowest dfi that can be reached using one back edge +(non-tree edge) + +-lowpoint +Dfi of the vertex with lowest dfi that can be reached using any number of +tree edges plus one back edge + + +A root vertex is a virtual vertex not contained in the original ugraph. +The root vertex represents another vertex in a biconnected component (block) +which is a child of the biconnected component the represented vertex is in. +The only field that it has in common with other vertices is the +adjacency list links array: + +-isroot +always true, indicates that this vertex is a virtual root + +-rootparent +The vertex which this root represents + +-rootchild +The only dfs child of the original vertex which is contained in the +root verticis biconnected component + +-adjlistlinks +See adjlistlinks of a normal vertex + + +A half edge is a table with the following fields: + +-links +A table with two fields with keys 0 and 1, containing the neighboring +half edges in the adjacency list of the vertex these edges originate from. + +-target +The vertex the half edge leads to + +-twin +The twin half edge which connects the two vertices in the opposite direction + +-shortcircuit +True if the half edge was inserted in order to make a short circuit for the +algorithm. The edge will be removed at the end. + +The BoyerMyrvold2004 class has the following fields: + +-inputgraph +The original ugraph given to the algorithm + +-numvertices +The number of vertices of the graph + +-vertices +The vertex table with depth-first search indices as keys + +-verticesbyinputvertex +The vertex table with input vertices as keys + +-verticesbylowpoint +The vertex table with low points as keys + +-shortcircuitedges +An array of all short circuit half edges +(which may not be in the original graph and will be removed at the end) + +--]] + +local BM = {} +require("pgf.gd.planar").BoyerMyrvold2004 = BM + +-- imports +local Storage = require "pgf.gd.lib.Storage" +local LinkedList = require "pgf.gd.planar.LinkedList" +local Embedding = require "pgf.gd.planar.Embedding" + +-- create class properties +BM.__index = BM + +function BM.new() + local t = {} + setmetatable(t, BM) + return t +end + +-- initializes some data structures at the beginning +-- takes the ugraph of the layout algorithm as input +function BM:init(g) + self.inputgraph = g + self.numvertices = #g.vertices + self.vertices = {} + self.verticesbyinputvertex = Storage.new() + self.verticesbylowpoint = Storage.newTableStorage() + self.shortcircuitedges = {} + for _, inputvertex in ipairs(self.inputgraph.vertices) do + local vertex = { + sign = 1, + childlist = LinkedList.new(), + adjlistlinks = {}, + pertinentroots = LinkedList.new(), + inputvertex = inputvertex, + } + setmetatable(vertex, Embedding.vertexmetatable) + self.verticesbyinputvertex[inputvertex] = vertex + end +end + +--[[ +local function nilmax(a, b) + if a == nil then return b end + if b == nil then return a end + return math.max(a, b) +end + +local function nilmin(a, b) + if a == nil then return b end + if b == nil then return a end + return math.min(a, b) +end +--]] + +-- the depth-first search of the preprocessing +function BM:predfs(inputvertex, parent) + local dfi = #self.vertices + 1 + local vertex = self.verticesbyinputvertex[inputvertex] + self.vertices[dfi] = vertex + -- set the dfs infos in the vertex + vertex.dfi = dfi + vertex.dfsparent = parent + vertex.leastancestor = dfi + vertex.lowpoint = dfi + -- find neighbors + for _, arc in ipairs(self.inputgraph:outgoing(inputvertex)) do + local ninputvertex = arc.head + assert(ninputvertex ~= inputvertex, "Self-loop detected!") + local nvertex = self.verticesbyinputvertex[ninputvertex] + if nvertex.dfi == nil then + -- new vertex discovered + self:predfs(ninputvertex, vertex) -- recursive call + vertex.lowpoint = math.min(vertex.lowpoint, nvertex.lowpoint) + elseif parent and ninputvertex ~= parent.inputvertex then + -- back edge found + vertex.leastancestor = math.min(vertex.leastancestor, nvertex.dfi) + vertex.lowpoint = math.min(vertex.lowpoint, nvertex.dfi) + end + end + -- put vertex into lowpoint sort bucket + table.insert(self.verticesbylowpoint[vertex.lowpoint], vertex) +end + +-- the preprocessing at the beginning of the algorithm +-- does the depth-first search and the bucket sort for the child lists +function BM:preprocess() + -- make dfs starting at an arbitrary vertex + self:predfs(self.inputgraph.vertices[1]) + -- create separated child lists with bucket sort + for i = 1, self.numvertices do + for _, vertex in ipairs(self.verticesbylowpoint[i]) do + if vertex.dfsparent then + vertex.childlistelement + = vertex.dfsparent.childlist:addback(vertex) + end + end + end +end + +-- adds tree edges and the corresponding virtual root vertices +-- of the currentvertex +function BM:add_trivial_edges(vertex) + -- find all dfs children + for _, arc in ipairs(self.inputgraph:outgoing(vertex.inputvertex)) do + local nvertex = self.verticesbyinputvertex[arc.head] + if nvertex.dfsparent == vertex then + -- create root vertex + local rootvertex = { + isroot = true, + rootparent = vertex, + rootchild = nvertex, + adjlistlinks = {}, + name = tostring(vertex) .. "^" .. tostring(nvertex) + } + setmetatable(rootvertex, Embedding.vertexmetatable) + nvertex.parentroot = rootvertex + -- create half edges + local halfedge1 = {target = nvertex, links = {}} + local halfedge2 = {target = rootvertex, links = {}} + halfedge1.twin = halfedge2 + halfedge2.twin = halfedge1 + -- create circular adjacency lists + halfedge1.links[0] = halfedge1 + halfedge1.links[1] = halfedge1 + halfedge2.links[0] = halfedge2 + halfedge2.links[1] = halfedge2 + -- create links to adjacency lists + rootvertex.adjlistlinks[0] = halfedge1 + rootvertex.adjlistlinks[1] = halfedge1 + nvertex.adjlistlinks[0] = halfedge2 + nvertex.adjlistlinks[1] = halfedge2 + end + end +end + +-- for the external face vertex which was entered through link vin +-- returns the successor on the external face and the link through +-- which it was entered +local function get_successor_on_external_face(vertex, vin) + local halfedge = vertex.adjlistlinks[1 - vin] + local svertex = halfedge.target + local sin + if vertex.adjlistlinks[0] == vertex.adjlistlinks[1] then + sin = vin + elseif svertex.adjlistlinks[0].twin == halfedge then + sin = 0 + else + sin = 1 + end + return svertex, sin +end + +-- the "walkup", used to identify the pertinent subgraph, +-- i. e. the subgraph that contains end points of backedges +-- for one backedge this function will mark all virtual roots +-- as pertinent that lie on the path between the backedge and the current vertex +-- backvertex: a vertex that is an endpoint of a backedge to the current vertex +-- currentvertex: the vertex of the current step +-- returns a root vertex of the current step, if one was found +local function walkup(backvertex, currentvertex) + local currentindex = currentvertex.dfi + -- set the backedgeflag + backvertex.backedgeindex = currentindex + -- initialize traversal variables for both directions + local x, xin, y, yin = backvertex, 1, backvertex, 0 + while x ~= currentvertex do + if x.visited == currentindex or y.visited == currentindex then + -- we found a path that already has the pertinent roots marked + return nil + end + -- mark vertices as visited for later calls + x.visited = currentindex + y.visited = currentindex + + -- check for rootvertex + local rootvertex + if x.isroot then + rootvertex = x + elseif y.isroot then + rootvertex = y + end + if rootvertex then + local rootchild = rootvertex.rootchild + local rootparent = rootvertex.rootparent + if rootvertex.rootparent == currentvertex then + -- we found the other end of the back edge + return rootvertex + elseif rootchild.lowpoint < currentindex then + -- the block we just traversed is externally active + rootvertex.pertinentrootselement + = rootparent.pertinentroots:addback(rootvertex) + else + -- the block we just traversed is internally active + rootvertex.pertinentrootselement + = rootparent.pertinentroots:addfront(rootvertex) + end + -- jump to parent block + x, xin, y, yin = rootvertex.rootparent, 1, rootvertex.rootparent, 0 + else + -- just continue on the external face + x, xin = get_successor_on_external_face(x, xin) + y, yin = get_successor_on_external_face(y, yin) + end + end +end + +-- inverts the adjacency of a vertex +-- i. e. reverses the order of the adjacency list and flips the links +local function invert_adjacency(vertex) + -- reverse the list + for halfedge in Embedding.adjacency_iterator(vertex.adjlistlinks[0]) do + halfedge.links[0], halfedge.links[1] + = halfedge.links[1], halfedge.links[0] + end + -- flip links + vertex.adjlistlinks[0], vertex.adjlistlinks[1] + = vertex.adjlistlinks[1], vertex.adjlistlinks[0] +end + +-- merges two blocks by merging the virtual root of the child block +-- into it's parent, while making sure the external face stays consistent +-- by flipping the root block if needed +-- mergeinfo contains four fields: +-- root - the virtual root vertex +-- parent - it's parent +-- rout - the link of the root through which we have exited it +-- during the walkdown +-- pin - the link of the parent through which we have entered it +-- during the walkdown +local function mergeblocks(mergeinfo) + local root = mergeinfo.root + local parent = mergeinfo.parent + local rout = mergeinfo.rootout + local pin = mergeinfo.parentin + if pin == rout then + -- flip required + invert_adjacency(root) + root.rootchild.sign = -1 + --rout = 1 - rout -- not needed + end + + -- redirect edges of the root vertex + for halfedge in Embedding.adjacency_iterator(root.adjlistlinks[0]) do + halfedge.twin.target = parent + end + + -- remove block from data structures + root.rootchild.parentroot = nil + parent.pertinentroots:remove(root.pertinentrootselement) + parent.childlist:remove(root.rootchild.childlistelement) + + -- merge adjacency lists + parent.adjlistlinks[0].links[1] = root.adjlistlinks[1] + parent.adjlistlinks[1].links[0] = root.adjlistlinks[0] + root.adjlistlinks[0].links[1] = parent.adjlistlinks[1] + root.adjlistlinks[1].links[0] = parent.adjlistlinks[0] + parent.adjlistlinks[pin] = root.adjlistlinks[pin] +end + +-- inserts a half edge pointing to "to" into the adjacency list of "from", +-- replacing the link "linkindex" +local function insert_half_edge(from, linkindex, to) + local halfedge = {target = to, links = {}} + halfedge.links[ linkindex] = from.adjlistlinks[ linkindex] + halfedge.links[1 - linkindex] = from.adjlistlinks[1 - linkindex] + from.adjlistlinks[ linkindex].links[1 - linkindex] = halfedge + from.adjlistlinks[1 - linkindex].links[ linkindex] = halfedge + from.adjlistlinks[linkindex] = halfedge + return halfedge +end + +-- connect the vertices x and y through the links xout and yin +-- if shortcircuit is true, the edge will be marked as a short circuit edge +-- and removed at the end of the algorithm +function BM:embed_edge(x, xout, y, yin, shortcircuit) + -- create half edges + local halfedgex = insert_half_edge(x, xout, y) + local halfedgey = insert_half_edge(y, yin, x) + halfedgex.twin = halfedgey + halfedgey.twin = halfedgex + -- short circuit handling + if shortcircuit then + halfedgex.shortcircuit = true + halfedgey.shortcircuit = true + table.insert(self.shortcircuitedges, halfedgex) + table.insert(self.shortcircuitedges, halfedgey) + end +end + +-- returns true if the given vertex is pertinent at the current step +local function pertinent(vertex, currentindex) + return vertex.backedgeindex == currentindex + or not vertex.pertinentroots:empty() +end + +-- returns true if the given vertex is externally active at the current step +local function externally_active(vertex, currentindex) + return vertex.leastancestor < currentindex + or (not vertex.childlist:empty() + and vertex.childlist:first().lowpoint < currentindex) +end + +-- the "walkdown", which merges the pertinent subgraph and embeds +-- back and short circuit edges +-- childrootvertex - a root vertex of the current vertex +-- which the walkdown will start at +-- currentvertex - the vertex of the current step +function BM:walkdown(childrootvertex, currentvertex) + local currentindex = currentvertex.dfi + local mergestack = {} + local numinsertededges = 0 -- to return the number for count check + -- two walkdowns into both directions + for vout = 0,1 do + -- initialize the traversal variables + local w, win = get_successor_on_external_face(childrootvertex, 1 - vout) + while w ~= childrootvertex do + if w.backedgeindex == currentindex then + -- we found a backedge endpoint + -- merge all pertinent roots we found + while #mergestack > 0 do + mergeblocks(table.remove(mergestack)) + end + -- embed the back edge + self:embed_edge(childrootvertex, vout, w, win) + numinsertededges = numinsertededges + 1 + w.backedgeindex = 0 -- this shouldn't be necessary + end + if not w.pertinentroots:empty() then + -- we found a pertinent vertex with child blocks + -- create merge info for the later merge + local mergeinfo = {} + mergeinfo.parent = w + mergeinfo.parentin = win + local rootvertex = w.pertinentroots:first() + mergeinfo.root = rootvertex + -- check both directions for active vertices + local x, xin = get_successor_on_external_face(rootvertex, 1) + local y, yin = get_successor_on_external_face(rootvertex, 0) + local xpertinent = pertinent(x, currentindex) + local xexternallyactive = externally_active(x, currentindex) + local ypertinent = pertinent(y, currentindex) + local yexternallyactive = externally_active(y, currentindex) + -- chose the direction with the best vertex + if xpertinent and not xexternallyactive then + w, win = x, xin + mergeinfo.rootout = 0 + elseif ypertinent and not yexternallyactive then + w, win = y, yin + mergeinfo.rootout = 1 + elseif xpertinent then + w, win = x, xin + mergeinfo.rootout = 0 + else + w, win = y, yin + mergeinfo.rootout = 1 + end + -- this is what the paper says, but it might cause problems + -- not sure though... + --[[if w == x then + mergeinfo.rootout = 0 + else + mergeinfo.rootout = 1 + end--]] + table.insert(mergestack, mergeinfo) + elseif not pertinent(w, currentindex) + and not externally_active(w, currentindex) then + -- nothing to see here, just continue on the external face + w, win = get_successor_on_external_face(w, win) + else + -- this is a stopping vertex, walkdown will end here + -- paper puts this into the if, + -- but this should always be the case, i think + assert(childrootvertex.rootchild.lowpoint < currentindex) + if #mergestack == 0 then + -- we're in the block we started at, so we embed a back edge + self:embed_edge(childrootvertex, vout, w, win, true) + end + break + end + end + if #mergestack > 0 then + -- this means, there is a pertinent vertex blocked by stop vertices, + -- so the graph is not planar and we can skip the second walkdown + break + end + end + return numinsertededges +end + +-- embeds the back edges for the current vertex +-- walkup and walkdown are called from here +-- returns true, if all back edges could be embedded +function BM:add_back_edges(vertex) + local pertinentroots = {} -- not in the paper + local numbackedges = 0 + -- find all back edges to vertices with lower dfi + for _, arc in ipairs(self.inputgraph:outgoing(vertex.inputvertex)) do + local nvertex = self.verticesbyinputvertex[arc.head] + if nvertex.dfi > vertex.dfi + and nvertex.dfsparent ~= vertex + and nvertex ~= vertex.dfsparent then + numbackedges = numbackedges + 1 + -- do the walkup + local rootvertex = walkup(nvertex, vertex) + if rootvertex then + -- remember the root vertex the walkup found, so we don't + -- have to call the walkdown for all root vertices + -- (or even know what the root vertices are) + table.insert(pertinentroots, rootvertex) + end + end + end + -- for all root vertices the walkup found + local insertededges = 0 + while #pertinentroots > 0 do + -- do the walkdown + insertededges = insertededges + + self:walkdown(table.remove(pertinentroots), vertex) + end + if insertededges ~= numbackedges then + -- not all back edges could be embedded -> graph is not planar + return false + end + return true +end + +-- the depth-first search of the postprocessing +-- flips the blocks according to the sign field +function BM:postdfs(vertex, sign) + sign = sign or 1 + local root = vertex.parentroot + if root then + sign = 1 + else + sign = sign * vertex.sign + end + + if sign == -1 then + -- number of flips is odd, so we need to flip here + invert_adjacency(vertex) + end + + -- for all dfs children + for _, arc in ipairs(self.inputgraph:outgoing(vertex.inputvertex)) do + local nvertex = self.verticesbyinputvertex[arc.head] + if nvertex.dfsparent == vertex then + -- recursive call + self:postdfs(nvertex, sign) + end + end +end + +-- the postprocessing at the end of the algorithm +-- calls the post depth-first search, +-- removes the short circuit edges from the adjacency lists, +-- adjusts the links of the vertices, +-- merges root vertices +-- and cleans up the vertices +function BM:postprocess() + -- flip components + self:postdfs(self.vertices[1]) + + -- unlink the short circuit edges + for _, halfedge in ipairs(self.shortcircuitedges) do + halfedge.links[0].links[1] = halfedge.links[1] + halfedge.links[1].links[0] = halfedge.links[0] + end + + -- vertex loop + local rootvertices = {} + local edgetoface = {} + for _, vertex in ipairs(self.vertices) do + -- check for root vertex and save it + local root = vertex.parentroot + if root then + table.insert(rootvertices, root) + end + + -- clean up links and create adjacency matrix + local link = vertex.adjlistlinks[0] + local adjmat = {} + vertex.adjmat = adjmat + if link then + -- make sure the link points to a half edge + -- that is no short circuit edge + while link.shortcircuit do + link = link.links[0] + end + -- create link + vertex.link = link + + -- create adjacency matrix + for halfedge in Embedding.adjacency_iterator(link) do + setmetatable(halfedge, Embedding.halfedgemetatable) + local target = halfedge.target + if target.isroot then + target = target.rootparent + end + adjmat[target] = halfedge + end + end + + -- clean up vertex + vertex.sign = nil + vertex.childlist = nil + vertex.adjlistlinks = nil + vertex.pertinentroots = nil + vertex.dfi = nil + vertex.dfsparent = nil + vertex.leastancestor = nil + vertex.lowpoint = nil + vertex.parentroot = nil + end + + -- root vertex loop + for _, root in ipairs(rootvertices) do + -- make sure the links point to a half edges + -- that are no short circuit edge + local link = root.adjlistlinks[0] + while link.shortcircuit do + link = link.links[0] + end + + -- merge into parent + local rootparent = root.rootparent + local parentlink = rootparent.link + local adjmat = rootparent.adjmat + for halfedge in Embedding.adjacency_iterator(link) do + setmetatable(halfedge, Embedding.halfedgemetatable) + halfedge.twin.target = rootparent + adjmat[halfedge.target] = halfedge + end + if parentlink == nil then + assert(rootparent.link == nil) + rootparent.link = link + else + -- merge adjacency lists + parentlink.links[0].links[1] = link + link.links[0].links[1] = parentlink + local tmp = link.links[0] + link.links[0] = parentlink.links[0] + parentlink.links[0] = tmp + end + end +end + +-- the entry point of the algorithm +-- returns the array of vertices +-- the vertices now only contain the inputvertex field +-- and a field named "link" which contains an arbitrary half edge +-- from the respective adjacency list +-- the adjacency lists are in a circular order in respect to the plane graph +function BM:run() + self:preprocess() + -- main loop over all vertices from lowest dfi to highest + for i = self.numvertices, 1, -1 do + local vertex = self.vertices[i] + self:add_trivial_edges(vertex) + if not self:add_back_edges(vertex) then + -- graph not planar + return nil + end + end + self:postprocess() + local embedding = Embedding.new() + embedding.vertices = self.vertices + return embedding +end + +return BM diff --git a/graphics/pgf/base/tex/generic/graphdrawing/lua/pgf/gd/planar/Embedding.lua b/graphics/pgf/base/tex/generic/graphdrawing/lua/pgf/gd/planar/Embedding.lua new file mode 100644 index 0000000000..8d9d3b53b1 --- /dev/null +++ b/graphics/pgf/base/tex/generic/graphdrawing/lua/pgf/gd/planar/Embedding.lua @@ -0,0 +1,788 @@ +local E = {} + +require("pgf.gd.planar").Embedding = E + +-- includes +local LinkedList = require("pgf.gd.planar.LinkedList") + +E.vertexmetatable = { + __tostring = function(v) + if v.name then + return v.name + elseif v.inputvertex then + return v.inputvertex.name + else + return tostring(v) + end + end +} + +E.halfedgemetatable = { + __tostring = function(e) + return tostring(e.twin.target) + .. " -> " + .. tostring(e.target) + end +} + +-- create class properties +E.__index = E + +function E.new() + local t = { + vertices = {}, + } + setmetatable(t, E) + return t +end + +function E:add_vertex(name, inputvertex, virtual) + virtual = virtual or nil + local vertex = { + adjmat = {}, + name = name, + inputvertex = inputvertex, + virtual = virtual, + } + setmetatable(vertex, E.vertexmetatable) + table.insert(self.vertices, vertex) + return vertex +end + +function E:add_edge(v1, v2, after1, after2, virtual) + assert(v1.link == nil or v1 == after1.twin.target) + assert(v2.link == nil or v2 == after2.twin.target) + assert(v1.adjmat[v2] == nil) + assert(v2.adjmat[v1] == nil) + + virtual = virtual or nil + + local halfedge1 = { + target = v2, + virtual = virtual, + links = {}, + } + local halfedge2 = { + target = v1, + virtual = virtual, + links = {}, + } + halfedge1.twin = halfedge2 + halfedge2.twin = halfedge1 + + setmetatable(halfedge1, E.halfedgemetatable) + setmetatable(halfedge2, E.halfedgemetatable) + + if v1.link == nil then + v1.link = halfedge1 + halfedge1.links[0] = halfedge1 + halfedge1.links[1] = halfedge1 + else + halfedge1.links[0] = after1.links[0] + after1.links[0].links[1] = halfedge1 + halfedge1.links[1] = after1 + after1.links[0] = halfedge1 + end + + if v2.link == nil then + v2.link = halfedge2 + halfedge2.links[0] = halfedge2 + halfedge2.links[1] = halfedge2 + else + halfedge2.links[0] = after2.links[0] + after2.links[0].links[1] = halfedge2 + halfedge2.links[1] = after2 + after2.links[0] = halfedge2 + end + + v1.adjmat[v2] = halfedge1 + v2.adjmat[v1] = halfedge2 + + return halfedge1, halfedge2 +end + +function E:remove_virtual() + local virtuals = {} + for i, v in ipairs(self.vertices) do + if v.virtual then + table.insert(virtuals, i) + else + local start = v.link + local current = start + repeat + current = current.links[0] + if current.virtual then + current.links[0].links[1] = current.links[1] + current.links[1].links[0] = current.links[0] + v.adjmat[current.target] = nil + current.target.adjmat[v] = nil + end + until current == start + end + end + for i = #virtuals, 1, -1 do + self.vertices[virtuals[i]] = self.vertices[#self.vertices] + table.remove(self.vertices) + end +end + +-- for the use in for-loops +-- iterates over the adjacency list of a vertex +-- given a half edge to start and a direction (0 or 1, default 0) +function E.adjacency_iterator(halfedge, direction) + direction = direction or 0 + local function next_edge(startedge, prevedge) + if prevedge == nil then + return startedge + else + local nextedge = prevedge.links[direction] + if nextedge ~= startedge then + return nextedge + else + return nil + end + end + end + return next_edge, halfedge, nil +end + +function E.face_iterator(halfedge, direction) + direction = direction or 0 + local function next_edge(startedge, prevedge) + if prevedge == nil then + return startedge + else + local nextedge = prevedge.twin.links[1 - direction] + if nextedge ~= startedge then + return nextedge + else + return nil + end + end + end + return next_edge, halfedge, nil +end + +function E:triangulate() + local visited = {} + for _, vertex in ipairs(self.vertices) do + for start in E.adjacency_iterator(vertex.link) do + if not visited[start] then + local prev = start + local beforestart = start.links[0].twin + local current = start.twin.links[1] + local next = current.twin.links[1] + visited[start] = true + visited[current] = true + visited[next] = true + while next ~= beforestart do + local halfedge1, halfedge2 + if vertex ~= current.target + and not vertex.adjmat[current.target] then + halfedge1, halfedge2 = self:add_edge( + vertex, current.target, + prev, next, + true + ) + + prev = halfedge1 + current = next + next = next.twin.links[1] + elseif not prev.target.adjmat[next.target] then + halfedge1, halfedge2 = self:add_edge( + prev.target, next.target, + current, next.twin.links[1], + true + ) + + current = halfedge1 + next = halfedge2.links[1] + else + local helper = next.twin.links[1] + halfedge1, halfedge2 = self:add_edge( + current.target, helper.target, + next, helper.twin.links[1], + true + ) + + next = halfedge1 + end + + visited[next] = true + visited[halfedge1] = true + visited[halfedge2] = true + end + end + end + end +end + +function E:canonical_order(v1, v2, vn) + local n = #self.vertices + local order = { v1 } + local marks = { [v1] = "ordered", [v2] = 0 } + local visited = {} + local vk = v1 + local candidates = LinkedList.new() + local listelements = {} + for k = 1, n-2 do + for halfedge in E.adjacency_iterator(vk.link) do + local vertex = halfedge.target + if vertex ~= vn then + local twin = halfedge.twin + visited[twin] = true + if marks[vertex] == nil then + marks[vertex] = "visited" + elseif marks[vertex] ~= "ordered" then + local neighbor1 = visited[twin.links[0]] + local neighbor2 = visited[twin.links[1]] + if marks[vertex] == "visited" then + if neighbor1 or neighbor2 then + marks[vertex] = 1 + listelements[vertex] = candidates:addback(vertex) + else + marks[vertex] = 2 + end + else + if neighbor1 == neighbor2 then + if neighbor1 and neighbor2 then + marks[vertex] = marks[vertex] - 1 + else + marks[vertex] = marks[vertex] + 1 + end + if marks[vertex] == 1 then + listelements[vertex] + = candidates:addback(vertex) + elseif listelements[vertex] then + candidates:remove(listelements[vertex]) + listelements[vertex] = nil + end + end + end + end + end + end + vk = candidates:popfirst() + order[k+1] = vk + marks[vk] = "ordered" + end + order[n] = vn + return order +end + +function E:get_biggest_face() + local number = 0 + local edge + local visited = {} + for _, vertex in ipairs(self.vertices) do + for start in E.adjacency_iterator(vertex.link) do + local count = 0 + if not visited[start] then + visited[start] = true + local current = start + repeat + count = count + 1 + current = current.twin.links[1] + until current == start + if count > number then + number = count + edge = start + end + end + end + end + return edge, number +end + +function E:surround_by_triangle(faceedge, facesize) + local divisor = 3 + if facesize > 3 then + divisor = 4 + end + local basenodes = math.floor(facesize / divisor) + local extranodes = facesize % divisor + local attachnodes = { basenodes, basenodes, basenodes } + if facesize > 3 then + attachnodes[2] = basenodes * 2 + end + for i = 1,extranodes do + attachnodes[i] = attachnodes[i] + 1 + end + + local v = { + self:add_vertex("$v_1$", nil, true), + self:add_vertex("$v_n$", nil, true), + self:add_vertex("$v_2$", nil, true) + } + for i = 1,3 do + local currentv = v[i] + local nextv = v[i % 3 + 1] + self:add_edge(currentv, nextv, currentv.link, nextv.link, true) + end + + local current = faceedge + local next = current.twin.links[1] + for i = 1,3 do + local vertex = v[i] + local otheredge = vertex.adjmat[v[i % 3 + 1]] + local previnserted = otheredge.links[1] + for count = 1, attachnodes[i] do + if not vertex.adjmat[current.target] then + previnserted, _ = self:add_edge( + vertex, current.target, + previnserted, next, + true + ) + end + + current = next + next = next.twin.links[1] + end + if not vertex.adjmat[current.target] then + previnserted, _ = self:add_edge( + vertex, current.target, + previnserted, next, + true + ) + current = previnserted + end + end + return v[1], v[3], v[2] +end + +function E:improve() + local pairdata = {} + local inpair = {} + for i, v1 in ipairs(self.vertices) do + for j = i + 1, #self.vertices do + local v2 = self.vertices[j] + local pd = self:find_pair_components(v1, v2) + if pd then + inpair[v1] = true + inpair[v2] = true + table.insert(pairdata, pd) + end + end + if not inpair[v1] then + local pd = self:find_pair_components(v1, nil) + if pd then + inpair[v1] = true + table.insert(pairdata, pd) + end + end + end + + local changed + local runs = 1 + local edgepositions = {} + repeat + changed = false + for i, pd in ipairs(pairdata) do + self:improve_separation_pair(pd) + end + -- check for changes + for i, v in ipairs(self.vertices) do + local start = v.link + local current = start + local counter = 1 + repeat + if counter ~= edgepositions[current] then + changed = true + edgepositions[current] = counter + end + counter = counter + 1 + current = current.links[0] + until current == start + end + runs = runs + 1 + until changed == false or runs > 100 +end + +function E:find_pair_components(v1, v2) + local visited = {} + local companchors = {} + local edgecomps = {} + local compvertices = {} + local islinear = {} + local edgeindices = {} + + local pair = { v1, v2 } + local start = v1.link + local current = start + local edgeindex = 1 + -- start searches from v1 + repeat + edgeindices[current] = edgeindex + edgeindex = edgeindex + 1 + if not edgecomps[current] then + local compindex = #companchors + 1 + local ca, il + edgecomps[current] = compindex + compvertices[compindex] = {} + local target = current.target + if target == v2 then + edgecomps[current.twin] = compindex + ca = 3 + il = true + else + ca, il = self:component_dfs( + target, + pair, + visited, + edgecomps, + compvertices[compindex], + compindex + ) + end + companchors[compindex] = ca + islinear[compindex] = il + end + current = current.links[0] + until current == start + + if v2 then + start = v2.link + current = start + local lastincomp = true + local edgeindex = 1 + -- now find the remaining blocks at v2 + repeat + edgeindices[current] = edgeindex + edgeindex = edgeindex + 1 + if not edgecomps[current] then + local compindex = #companchors + 1 + edgecomps[current] = compindex + compvertices[compindex] = {} + self:component_dfs( + current.target, + pair, + visited, + edgecomps, + compvertices[compindex], + compindex + ) + companchors[compindex] = 2 + end + current = current.links[0] + until current == start + end + + -- init compedges, tricomps, twocomps + local tricomps = {} + local twocomps = {{}, {}} + for i, anchors in ipairs(companchors) do + if anchors == 3 then + table.insert(tricomps, i) + else + table.insert(twocomps[anchors], i) + end + end + + local flipimmune = #tricomps == 2 + and (islinear[tricomps[1]] or islinear[tricomps[2]]) + if (#tricomps < 2 or flipimmune) + and (v2 ~= nil or #twocomps[1] < 2) then + return nil + end + + -- order tri comps cyclic + local function sorter(a, b) + return #compvertices[a] < #compvertices[b] + end + + table.sort(tricomps, sorter) + + -- determine order of comps + local numtricomps = #tricomps + local comporder = { {}, {} } + local bottom = math.ceil(numtricomps / 2) + local top = bottom + 1 + for i, comp in ipairs(tricomps) do + if i % 2 == 1 then + comporder[1][bottom] = comp + comporder[2][numtricomps - bottom + 1] = comp + bottom = bottom - 1 + else + comporder[1][top] = comp + comporder[2][numtricomps - top + 1] = comp + top = top + 1 + end + end + + local pairdata = { + pair = pair, + companchors = companchors, + edgecomps = edgecomps, + edgeindices = edgeindices, + compvertices = compvertices, + tricomps = tricomps, + twocomps = twocomps, + comporder = comporder, + } + return pairdata +end + +function E:component_dfs(v, pair, visited, edgecomps, compvertices, compindex) + visited[v] = true + local start = v.link + local current = start + local companchors = 1 + local numedges = 0 + local islinear = true + table.insert(compvertices, v) + repeat + numedges = numedges + 1 + local target = current.target + if target == pair[1] or target == pair[2] then + edgecomps[current.twin] = compindex + if target == pair[2] then + companchors = 3 + end + elseif not visited[target] then + local ca, il = self:component_dfs( + target, + pair, + visited, + edgecomps, + compvertices, + compindex + ) + if ca == 3 then + companchors = 3 + end + islinear = islinear and il + end + current = current.links[0] + until current == start + return companchors, islinear and numedges == 2 +end + +function E:improve_separation_pair(pairdata) + local pair = pairdata.pair + local companchors = pairdata.companchors + local edgecomps = pairdata.edgecomps + local edgeindices = pairdata.edgeindices + local compvertices = pairdata.compvertices + local tricomps = pairdata.tricomps + local twocomps = pairdata.twocomps + local comporder = pairdata.comporder + local v1 = pair[1] + local v2 = pair[2] + + local compedges = {} + for i = 1, #companchors do + compedges[i] = {{}, {}} + end + + local numtricomps = #tricomps + local numtwocomps = { #twocomps[1], #twocomps[2] } + + -- find compedges + for i = 1, #pair do + -- first find an edge that is the first of a triconnected component + local start2 + if v2 then + start = pair[i].link + current = start + local last + repeat + local comp = edgecomps[current] + if companchors[comp] == 3 then + if last == nil then + last = comp + elseif last ~= comp then + start2 = current + break + end + end + current = current.links[0] + until current == start + else + start2 = pair[i].link + end + -- now list the edges by components + current = start2 + repeat + table.insert(compedges[edgecomps[current]][i], current) + current = current.links[0] + until current == start2 + end + + -- count edges on each side of tri comps + local edgecount = {} + for _, comp in ipairs(tricomps) do + edgecount[comp] = {} + for i = 1, #pair do + local count = 1 + local current = compedges[comp][i][1] + local other = pair[3 - i] + while current.target ~= other do + count = count + 1 + current = current.twin.links[0] + end + edgecount[comp][i] = count + end + end + + -- determine which comps have to be flipped + local flips = {} + local numflips = 0 + local allflipped = true + for i, comp in ipairs(comporder[1]) do + local side1, side2 + if i > numtricomps / 2 then + side1 = edgecount[comp][1] + side2 = edgecount[comp][2] + else + side1 = edgecount[comp][2] + side2 = edgecount[comp][1] + end + if side1 > side2 then + numflips = numflips + 1 + flips[comp] = true + elseif side1 < side2 then + allflipped = false + end + end + + if allflipped then + for i, comp in ipairs(tricomps) do + flips[comp] = false + end + else + for i, comp in ipairs(tricomps) do + if flips[comp] then + for _, v in ipairs(compvertices[comp]) do + local start = v.link + local current = start + repeat + current.links[0], current.links[1] + = current.links[1], current.links[0] + current = current.links[1] + until current == start + end + end + end + end + + -- order edges cyclic per component (one cycle for all tri comps) + for i = 1, #pair do + if v2 then + local co + if allflipped then + co = comporder[3 - i] + else + co = comporder[i] + end + + local id = co[numtricomps] + lastedges = compedges[id][i] + if flips[id] then + lastedge = lastedges[1] + else + lastedge = lastedges[#lastedges] + end + + -- tri comps + for _, id in ipairs(co) do + local edges = compedges[id][i] + local from + local to + local step + if flips[id] then + from = #edges + to = 1 + step = -1 + else + from = 1 + to = #edges + step = 1 + end + for k = from, to, step do + local edge = edges[k] + lastedge.links[0] = edge + edge.links[1] = lastedge + lastedge = edge + end + end + end + + -- two comps + for _, id in ipairs(twocomps[i]) do + lastedges = compedges[id][i] + lastedge = lastedges[#lastedges] + for _, edge in ipairs(compedges[id][i]) do + lastedge.links[0] = edge + edge.links[1] = lastedge + lastedge = edge + end + end + end + + -- now merge the cycles + for i = 1, #pair do + local outeredges = {} + -- find the biggest face of the tri comps + if v2 then + local biggestedge + local biggestsize + local biggestindex + local start = compedges[tricomps[1]][i][1] + local current = start + repeat + local size = self:get_face_size(current) + if not biggestedge or size > biggestsize + or (size == biggestsize + and edgeindices[current] > biggestindex) then + biggestedge = current + biggestsize = size + biggestindex = edgeindices[current] + end + current = current.links[0] + until current == start + outeredges[1] = biggestedge + end + + -- now for every two comp + for _, id in ipairs(twocomps[i]) do + local biggestedge + local biggestsize + local biggestindex + local start = compedges[id][i][1] + local current = start + repeat + local size = self:get_face_size(current) + if not biggestedge or size > biggestsize + or (size == biggestsize + and edgeindices[current] > biggestindex) then + biggestedge = current + biggestsize = size + biggestindex = edgeindices[current] + end + current = current.links[0] + until current == start + table.insert(outeredges, biggestedge) + end + + -- now merge all comps at the outer edges + local lastedge = outeredges[#outeredges].links[0] + for _, edge in ipairs(outeredges) do + local nextlastedge = edge.links[0] + lastedge.links[1] = edge + edge.links[0] = lastedge + lastedge = nextlastedge + end + end +end + +function E:get_face_size(halfedge) + local size = 0 + local current = halfedge + repeat + size = size + 1 + current = current.twin.links[1] + until current == halfedge + return size +end + +return E diff --git a/graphics/pgf/base/tex/generic/graphdrawing/lua/pgf/gd/planar/LinkedList.lua b/graphics/pgf/base/tex/generic/graphdrawing/lua/pgf/gd/planar/LinkedList.lua new file mode 100644 index 0000000000..35f8418fdf --- /dev/null +++ b/graphics/pgf/base/tex/generic/graphdrawing/lua/pgf/gd/planar/LinkedList.lua @@ -0,0 +1,88 @@ +local LinkedList = {} + +LinkedList.__index = LinkedList + +function LinkedList.new() + local list = {elements = {}} + setmetatable(list, LinkedList) + return list +end + +function LinkedList:addback(payload) + if payload == nil then + error("Need a payload!", 2) + end + local element = { payload = payload } + if self.head then + local tail = self.head.prev + self.head.prev = element + tail.next = element + element.next = self.head + element.prev = tail + else + self.head = element + element.next = element + element.prev = element + end + self.elements[element] = true + return element +end + +function LinkedList:addfront(payload) + self.head = self:addback(payload) + return self.head +end + +function LinkedList:remove(element) + if self.elements[element] == nil then + error("Element not in list!", 2) + end + if self.head == element then + if element.next == element then + self.head = nil + else + self.head = element.next + end + end + element.prev.next = element.next + element.next.prev = element.prev + self.elements[element] = nil +end + +function LinkedList:popfirst() + if self.head == nil then + return nil + end + local element = self.head + if element.next == element then + self.head = nil + else + self.head = element.next + element.next.prev = element.prev + element.prev.next = element.next + end + self.elements[element] = nil + return element.payload +end + +function LinkedList:poplast() + if self.head == nil then + return nil + end + self.head = self.head.prev + return self:popfirst() +end + +function LinkedList:first() + return self.head and self.head.payload +end + +function LinkedList:last() + return self.head and self.head.prev.payload +end + +function LinkedList:empty() + return self.head == nil +end + +return LinkedList diff --git a/graphics/pgf/base/tex/generic/graphdrawing/lua/pgf/gd/planar/List.lua b/graphics/pgf/base/tex/generic/graphdrawing/lua/pgf/gd/planar/List.lua new file mode 100644 index 0000000000..22a91e6571 --- /dev/null +++ b/graphics/pgf/base/tex/generic/graphdrawing/lua/pgf/gd/planar/List.lua @@ -0,0 +1,49 @@ +local List = {} + +List.__index = List + +function List.new() + local t = {first = 0, last = -1} + setmetatable(t, List) + return t +end + +function List:pushleft(value) + local first = self.first - 1 + self.first = first + self[first] = value +end + +function List:pushright(value) + local last = self.last + 1 + self.last = last + self[last] = value +end + +function List:popleft() + local first = self.first + if first > self.last then error("List is empty") end + local value = self[first] + self[first] = nil + self.first = first + 1 + return value +end + +function List:popright() + local last = self.last + if self.first > last then error("List is empty") end + local value = self[last] + self[last] = nil + self.last = last - 1 + return value +end + +function List:size() + return self.last - self.first + 1 +end + +function List:empty() + return self.last < self.first +end + +return List diff --git a/graphics/pgf/base/tex/generic/graphdrawing/lua/pgf/gd/planar/PDP.lua b/graphics/pgf/base/tex/generic/graphdrawing/lua/pgf/gd/planar/PDP.lua new file mode 100644 index 0000000000..8c159cf61d --- /dev/null +++ b/graphics/pgf/base/tex/generic/graphdrawing/lua/pgf/gd/planar/PDP.lua @@ -0,0 +1,576 @@ + +local PDP = {} +require("pgf.gd.planar").PDP = PDP + +-- Imports +local declare = require("pgf.gd.interface.InterfaceToAlgorithms").declare +local Storage = require "pgf.gd.lib.Storage" +local Coordinate = require "pgf.gd.model.Coordinate" +local Path = require "pgf.gd.model.Path" +--- +PDP.__index = PDP + +function PDP.new(ugraph, embedding, + delta, gamma, coolingfactor, + expiterations, + startrepexp, endrepexp, + startattexp, endattexp, + appthreshold, stretchthreshold, + stresscounterthreshold, + numdivisions) + local t = { + ugraph = ugraph, + embedding = embedding, + delta = delta , + gamma = gamma, + coolingfactor = coolingfactor, + expiterations = expiterations, + startrepexp = startrepexp, + endrepexp = endrepexp, + startattexp = startattexp, + endattexp = endattexp, + appthreshold = appthreshold, + stretchthreshold = stretchthreshold, + stresscounterthreshold = stresscounterthreshold, + numdivisions = numdivisions, + posxs = {}, + posys = {}, + cvsxs = {}, + cvsys = {}, + embeddingedges = {}, + edgeids = {}, + numedgeids = 0, + vertexids = {}, + numvertexids = 0, + vertexpairs1 = {}, + vertexpairs2 = {}, + pairconnected = {}, + edgepairsvertex = {}, + edgepairsedge = {}, + edgevertex1 = {}, + edgevertex2 = {}, + edgedeprecated = {}, + subdivisionedges = {}, + subdivisionvertices = {}, + temperature = 1, + } + + setmetatable(t, PDP) + return t +end + +function PDP:run() + self:normalize_size() + self:find_force_pairs() + + local delta = self.delta + local gamma = self.gamma + local coolingfactor = self.coolingfactor + local expiterations = self.expiterations + local startrepexp = self.startrepexp + local endattexp = self.endattexp + local startattexp = self.startattexp + local endrepexp = self.endrepexp + + local vertexpairs1 = self.vertexpairs1 + local vertexpairs2 = self.vertexpairs2 + local pairconnected = self.pairconnected + local edgepairsvertex = self.edgepairsvertex + local edgepairsedge = self.edgepairsedge + local edgevertex1 = self.edgevertex1 + local edgevertex2 = self.edgevertex2 + local edgedeprecated = self.edgedeprecated + + local forcexs = {} + local forceys = {} + local posxs = self.posxs + local posys = self.posys + local cvsxs = self.cvsxs + local cvsys = self.cvsys + local numcvs = {} + for i, v in ipairs(self.embedding.vertices) do + cvsxs[i] = {} + cvsys[i] = {} + posxs[i] = v.inputvertex.pos.x + posys[i] = v.inputvertex.pos.y + end + + local numorigvertices = self.numvertexids + local numorigedges = self.numedgeids + local numdivisions = self.numdivisions + local divdelta = delta / (numdivisions + 1) + local stresscounter = {} + for i = 1, self.numedgeids do + stresscounter[i] = 0 + end + + local appthreshold = self.appthreshold + local stretchthreshold = self.stretchthreshold + local stresscounterthreshold = self.stresscounterthreshold + + for i = 1, numorigedges do + local iv1 = self.embedding.vertices[edgevertex1[i]].inputvertex + local iv2 = self.embedding.vertices[edgevertex2[i]].inputvertex + local arc = self.ugraph:arc(iv1, iv2) + --TODO subdivide edge if desired + --self:subdivide_edge(i) + end + + -- main loop + local iteration = 0 + repeat + iteration = iteration + 1 + local temperature = self.temperature + local ratio = math.min(1, iteration / expiterations) + local repexp = startrepexp + (endrepexp - startrepexp) * ratio + local attexp = startattexp + (endattexp - startattexp) * ratio + for i = 1, self.numvertexids do + forcexs[i] = 0 + forceys[i] = 0 + numcvs[i] = 0 + end + -- vertex-vertex forces + for i = 1, #vertexpairs1 do + local id1 = vertexpairs1[i] + local id2 = vertexpairs2[i] + local diffx = posxs[id2] - posxs[id1] + local diffy = posys[id2] - posys[id1] + local dist2 = diffx * diffx + diffy * diffy + local dist = math.sqrt(dist2) + local dirx = diffx / dist + local diry = diffy / dist + assert(dist ~= 0) + + local useddelta = delta + local hasdivvertex = id1 > numorigvertices or id2 > numorigvertices + + -- calculate attractive force + if pairconnected[i] then + if hasdivvertex then + useddelta = divdelta + end + local mag = (dist / useddelta) ^ attexp * useddelta + local fax = mag * dirx + local fay = mag * diry + forcexs[id1] = forcexs[id1] + fax + forceys[id1] = forceys[id1] + fay + forcexs[id2] = forcexs[id2] - fax + forceys[id2] = forceys[id2] - fay + elseif hasdivvertex then + useddelta = gamma + end + + -- calculate repulsive force + local mag = (useddelta / dist) ^ repexp * useddelta + local frx = mag * dirx + local fry = mag * diry + forcexs[id1] = forcexs[id1] - frx + forceys[id1] = forceys[id1] - fry + forcexs[id2] = forcexs[id2] + frx + forceys[id2] = forceys[id2] + fry + end + + -- edge-vertex forces and collisions + for i = 1, #edgepairsvertex do + local edgeid = edgepairsedge[i] + if not edgedeprecated[edgeid] then + local id1 = edgepairsvertex[i] + local id2 = edgevertex1[edgeid] + local id3 = edgevertex2[edgeid] + assert(id2 ~= id1 and id3 ~= id1) + + local abx = posxs[id3] - posxs[id2] + local aby = posys[id3] - posys[id2] + local dab2 = abx * abx + aby * aby + local dab = math.sqrt(dab2) + assert(dab ~= 0) + local abnx = abx / dab + local abny = aby / dab + local avx = posxs[id1] - posxs[id2] + local avy = posys[id1] - posys[id2] + local daiv = abnx * avx + abny * avy + local ivx = posxs[id2] + abnx * daiv + local ivy = posys[id2] + abny * daiv + local vivx = ivx - posxs[id1] + local vivy = ivy - posys[id1] + local dviv2 = vivx * vivx + vivy * vivy + local dviv = math.sqrt(dviv2) + local afactor, bfactor = 1, 1 + local cvx + local cvy + if daiv < 0 then + cvx = -avx / 2 + cvy = -avy / 2 + local norm2 = cvx * cvx + cvy * cvy + bfactor = 1 + (cvx * abx + cvy * aby) / norm2 + elseif daiv > dab then + cvx = (abx - avx) / 2 + cvy = (aby - avy) / 2 + local norm2 = cvx * cvx + cvy * cvy + afactor = 1 - (cvx * abx + cvy * aby) / norm2 + else + if edgeid < numorigedges + and dviv < gamma * appthreshold + and dab > delta * stretchthreshold then + stresscounter[edgeid] = stresscounter[edgeid] + 1 + end + assert(dviv > 0) + cvx = vivx / 2 + cvy = vivy / 2 + -- calculate edge repulsive force + local dirx = -vivx / dviv + local diry = -vivy / dviv + local mag = (gamma / dviv) ^ repexp * gamma + local fex = mag * dirx + local fey = mag * diry + local abratio = daiv / dab + forcexs[id1] = forcexs[id1] + fex + forceys[id1] = forceys[id1] + fey + forcexs[id2] = forcexs[id2] - fex * (1 - abratio) + forceys[id2] = forceys[id2] - fey * (1 - abratio) + forcexs[id3] = forcexs[id3] - fex * abratio + forceys[id3] = forceys[id3] - fey * abratio + end + local nv = numcvs[id1] + 1 + local na = numcvs[id2] + 1 + local nb = numcvs[id3] + 1 + numcvs[id1] = nv + numcvs[id2] = na + numcvs[id3] = nb + cvsxs[id1][nv] = cvx + cvsys[id1][nv] = cvy + cvsxs[id2][na] = -cvx * afactor + cvsys[id2][na] = -cvy * afactor + cvsxs[id3][nb] = -cvx * bfactor + cvsys[id3][nb] = -cvy * bfactor + end + end + + -- clamp forces + local scalefactor = 1 + local collision = false + for i = 1, self.numvertexids do + local forcex = forcexs[i] + local forcey = forceys[i] + forcex = forcex * temperature + forcey = forcey * temperature + forcexs[i] = forcex + forceys[i] = forcey + local forcenorm2 = forcex * forcex + forcey * forcey + local forcenorm = math.sqrt(forcenorm2) + scalefactor = math.min(scalefactor, delta * 3 * temperature / forcenorm) + local cvys = cvsys[i] + for j, cvx in ipairs(cvsxs[i]) do + local cvy = cvys[j] + local cvnorm2 = cvx * cvx + cvy * cvy + local cvnorm = math.sqrt(cvnorm2) + local projforcenorm = (cvx * forcex + cvy * forcey) / cvnorm + if projforcenorm > 0 then + local factor = cvnorm * 0.9 / projforcenorm + if factor < scalefactor then + scalefactor = factor + collision = true + end + end + end + end + local moved = false + for i = 1, self.numvertexids do + local forcex = forcexs[i] * scalefactor + local forcey = forceys[i] * scalefactor + posxs[i] = posxs[i] + forcex + posys[i] = posys[i] + forcey + local forcenorm2 = forcex * forcex + forcey * forcey + if forcenorm2 > 0.0001 * delta * delta then moved = true end + end + + -- subdivide stressed edges + if numdivisions > 0 then + for i = 1, numorigedges do + if stresscounter[i] > stresscounterthreshold then + self:subdivide_edge(i) + stresscounter[i] = 0 + end + end + end + self.temperature = self.temperature * coolingfactor + until not collision and not moved + print("\nfinished PDP after " .. iteration .. " iterations") + + -- write the positions back + for i, v in ipairs(self.embedding.vertices) do + v.inputvertex.pos.x = posxs[i] + v.inputvertex.pos.y = posys[i] + end + + -- route the edges + for i = 1, self.numedgeids do + if self.subdivisionvertices[i] then + local iv1 = self.embedding.vertices[self.edgevertex1[i]].inputvertex + local iv2 = self.embedding.vertices[self.edgevertex2[i]].inputvertex + local arc = self.ugraph:arc(iv1, iv2) + local p = Path.new() + p:appendMoveto(arc.tail.pos:clone()) + for _, vid in ipairs(self.subdivisionvertices[i]) do + p:appendLineto(self.posxs[vid], self.posys[vid]) + end + p:appendLineto(arc.head.pos:clone()) + arc.path = p + end + end +end + +function PDP:subdivide_edge(edgeid) + assert(self.subdivisionedges[edgeid] == nil) + local numdivisions = self.numdivisions + local subdivisionedges = {} + local subdivisionvertices = {} + local id1 = self.edgevertex1[edgeid] + local id2 = self.edgevertex2[edgeid] + local x1 = self.posxs[id1] + local y1 = self.posys[id1] + local x2 = self.posxs[id2] + local y2 = self.posys[id2] + local prevvertexid = id1 + for i = 1, numdivisions do + -- create new edge and vertex + local newvertexid1 = self.numvertexids + i + table.insert(subdivisionvertices, newvertexid1) + self.posxs[newvertexid1] = (x1 * (numdivisions + 1 - i) + x2 * i) + / (numdivisions + 1) + self.posys[newvertexid1] = (y1 * (numdivisions + 1 - i) + y2 * i) + / (numdivisions + 1) + self.cvsxs[newvertexid1] = {} + self.cvsys[newvertexid1] = {} + + local newedgeid = self.numedgeids + i + table.insert(subdivisionedges, newedgeid) + table.insert(self.edgevertex1, prevvertexid) + table.insert(self.edgevertex2, newvertexid1) + prevvertexid = newvertexid1 + + -- pair the new vertex + -- with first vertex of the edge being divided + table.insert(self.vertexpairs1, self.edgevertex1[edgeid]) + table.insert(self.vertexpairs2, newvertexid1) + table.insert(self.pairconnected, i == 1) + + -- with second vertex of the edge being divided + table.insert(self.vertexpairs1, self.edgevertex2[edgeid]) + table.insert(self.vertexpairs2, newvertexid1) + table.insert(self.pairconnected, i == numdivisions) + + -- with each other + for j = i + 1, numdivisions do + local newvertexid2 = self.numvertexids + j + table.insert(self.vertexpairs1, newvertexid1) + table.insert(self.vertexpairs2, newvertexid2) + table.insert(self.pairconnected, j == i + 1) + end + + -- with new edges + -- before vertex + for j = 1, i - 1 do + local newedgeid = self.numedgeids + j + table.insert(self.edgepairsvertex, newvertexid1) + table.insert(self.edgepairsedge, newedgeid) + end + -- after vertex + for j = i + 2, numdivisions + 1 do + local newedgeid = self.numedgeids + j + table.insert(self.edgepairsvertex, newvertexid1) + table.insert(self.edgepairsedge, newedgeid) + end + + -- pair the new edges with vertices of the edge being divided + if i > 1 then + table.insert(self.edgepairsvertex, id1) + table.insert(self.edgepairsedge, newedgeid) + end + table.insert(self.edgepairsvertex, id2) + table.insert(self.edgepairsedge, newedgeid) + end + -- create last edge + table.insert(subdivisionedges, self.numedgeids + numdivisions + 1) + table.insert(self.edgevertex1, prevvertexid) + table.insert(self.edgevertex2, id2) + + -- pair last edge with first vertex of the edge being divided + table.insert(self.edgepairsvertex, id1) + table.insert(self.edgepairsedge, self.numedgeids + numdivisions + 1) + + self.subdivisionedges[edgeid] = subdivisionedges + self.subdivisionvertices[edgeid] = subdivisionvertices + + -- pair new edges and vertices with existing edges and vertices + local sameface = false + local start = self.embeddingedges[edgeid] + local twin = start.twin + local donevertices = { [start.target] = true, [twin.target] = true } + local doneedges = { [start] = true, [twin] = true } + local current = start.twin.links[1] + for twice = 1, 2 do + while current ~= start do + if current == twin then + sameface = true + end + + -- pair edge with the new vertices + -- or pair subdivision of edge with new vertices and edges + if not doneedges[current] then + local currentedgeid = self.edgeids[current] + if self.subdivisionvertices[currentedgeid] then + for _, vid in ipairs(self.subdivisionvertices[currentedgeid]) do + for i = 1, numdivisions do + local newvertexid = self.numvertexids + i + table.insert(self.vertexpairs1, vid) + table.insert(self.vertexpairs2, newvertexid) + self.pairconnected[#self.vertexpairs1] = false + end + for i = 1, numdivisions + 1 do + local newedgeid = self.numedgeids + i + table.insert(self.edgepairsvertex, vid) + table.insert(self.edgepairsedge, newedgeid) + end + end + for _, eid in ipairs(self.subdivisionedges[currentedgeid]) do + for i = 1, numdivisions do + local newvertexid = self.numvertexids + i + table.insert(self.edgepairsvertex, newvertexid) + table.insert(self.edgepairsedge, eid) + end + end + else + for i = 1, numdivisions do + local newvertexid = self.numvertexids + i + table.insert(self.edgepairsvertex, newvertexid) + table.insert(self.edgepairsedge, currentedgeid) + end + end + doneedges[current] = true + end + + -- pair target vertex with the new vertices and edges + local vertexid = self.vertexids[current.target] + if not donevertices[current.target] then + for i = 1, numdivisions do + local newvertexid = self.numvertexids + i + table.insert(self.vertexpairs1, vertexid) + table.insert(self.vertexpairs2, newvertexid) + self.pairconnected[#self.vertexpairs1] = false + end + for i = 1, numdivisions + 1 do + local newedgeid = self.numedgeids + i + table.insert(self.edgepairsvertex, vertexid) + table.insert(self.edgepairsedge, newedgeid) + end + end + current = current.twin.links[1] + end + start = self.embeddingedges[edgeid].twin + current = start.twin.links[1] + if sameface then + break + end + end + + self.edgedeprecated[edgeid] = true + self.numvertexids = self.numvertexids + numdivisions + self.numedgeids = self.numedgeids + numdivisions + 1 +end + +function PDP:find_force_pairs() + local donevertices = {} + -- number all vertices + local vertexids = self.vertexids + for i, v in ipairs(self.embedding.vertices) do + vertexids[v] = i + end + self.numvertexids = #self.embedding.vertices + + local edgeids = self.edgeids + local numedgeids = 0 + -- number all edges + for _, v in ipairs(self.embedding.vertices) do + local id = vertexids[v] + local start = v.link + local current = start + repeat + local targetid = vertexids[current.target] + if edgeids[current] == nil then + table.insert(self.edgevertex1, id) + table.insert(self.edgevertex2, targetid) + numedgeids = numedgeids + 1 + edgeids[current] = numedgeids + edgeids[current.twin] = numedgeids + self.embeddingedges[numedgeids] = current + end + current = current.links[0] + until current == start + end + + -- find all force pairs + for _, v in ipairs(self.embedding.vertices) do + local id = vertexids[v] + donevertices[id] = true + local vertexset = {} + local edgeset = {} + local start = v.link + repeat + local targetid = vertexids[start.target] + if vertexset[targetid] == nil and not donevertices[targetid] then + table.insert(self.pairconnected, true) + table.insert(self.vertexpairs1, id) + table.insert(self.vertexpairs2, targetid) + vertexset[targetid] = true + end + local current = start.twin.links[1] + while current.target ~= v do + local targetid = vertexids[current.target] + if vertexset[targetid] == nil and not donevertices[targetid] then + table.insert(self.pairconnected, self.ugraph:arc(v.inputvertex, current.target.inputvertex) ~= nil) + table.insert(self.vertexpairs1, id) + table.insert(self.vertexpairs2, targetid) + vertexset[targetid] = true + end + if edgeset[current] == nil then + table.insert(self.edgepairsvertex, id) + table.insert(self.edgepairsedge, edgeids[current]) + edgeset[current] = true + edgeset[current.twin] = true + end + current = current.twin.links[1] + end + start = start.links[0] + until start == v.link + end + + self.numedgeids = numedgeids +end + +function PDP:normalize_size() + local minx = math.huge + local maxx = -math.huge + local miny = math.huge + local maxy = -math.huge + + for _, v in ipairs(self.ugraph.vertices) do + minx = math.min(minx, v.pos.x) + maxx = math.max(maxx, v.pos.x) + miny = math.min(miny, v.pos.y) + maxy = math.max(maxy, v.pos.y) + end + + local area = (maxx - minx) * (maxy - miny) + local gridarea = #self.ugraph.vertices * self.delta * self.delta + + local scale = math.sqrt(gridarea) / math.sqrt(area) + + for _, v in ipairs(self.ugraph.vertices) do + v.pos = v.pos * scale + end +end + +-- done + +return PDP diff --git a/graphics/pgf/base/tex/generic/graphdrawing/lua/pgf/gd/planar/PlanarLayout.lua b/graphics/pgf/base/tex/generic/graphdrawing/lua/pgf/gd/planar/PlanarLayout.lua new file mode 100644 index 0000000000..9e5b8d72ec --- /dev/null +++ b/graphics/pgf/base/tex/generic/graphdrawing/lua/pgf/gd/planar/PlanarLayout.lua @@ -0,0 +1,159 @@ + + +local PlanarLayout = {} +require("pgf.gd.planar").PlanarLayout = PlanarLayout + +-- imports +local Coordinate = require "pgf.gd.model.Coordinate" +local Storage = require "pgf.gd.lib.Storage" +local BoyerMyrvold = require "pgf.gd.planar.BoyerMyrvold2004" +local ShiftMethod = require "pgf.gd.planar.ShiftMethod" +local Embedding = require "pgf.gd.planar.Embedding" +local PDP = require "pgf.gd.planar.PDP" +local InterfaceToAlgorithms = require("pgf.gd.interface.InterfaceToAlgorithms") +local createEdge = InterfaceToAlgorithms.createEdge +local createVertex = InterfaceToAlgorithms.createVertex + +InterfaceToAlgorithms.declare { + key = "planar layout", + algorithm = PlanarLayout, + preconditions = { + connected = true, + loop_free = true, + simple = true, + }, + postconditions = { + fixed = true, + }, + summary = [[" + The planar layout draws planar graphs without edge crossings. + "]], + documentation = [[" + The planar layout is a pipeline of algorithms to produce + a crossings-free drawing of a planar graph. + First a combinatorical embedding of the graph is created using + the Algorithm from Boyer and Myrvold. + The combinatorical Embedding is then being improved by + by the Sort and Flip algorithm and triangulated afterwards. + To determine the actual node positions the shift method + by de Fraysseix, Pach and Pollack is used. + Finally the force based Planar Drawing Postprocessing improves the drawing. + "]], + examples = { + [[" + \tikz \graph [nodes={draw, circle}] { + a -- { + b -- { + d -- i, + e, + f + }, + c -- { + g, + h + } + }, + f --[no span edge] a, + h --[no span edge] a, + i --[no span edge] g, + f --[no span edge] g, + c --[no span edge] d, + e --[no span edge] c + } + "]] + } +} + +function PlanarLayout:run() + --local file = io.open("timing.txt", "a") + + local options = self.digraph.options + + -- get embedding + local bm = BoyerMyrvold.new() + bm:init(self.ugraph) + local embedding = bm:run() + + assert(embedding, "Graph is not planar") + + --local start = os.clock() + if options["use sf"] then + embedding:improve() + end + + -- choose external face + local exedge, exsize = embedding:get_biggest_face() + + -- surround graph with triangle + local v1, v2, vn = embedding:surround_by_triangle(exedge, exsize) + + -- make maximal planar + embedding:triangulate() + + if options["show virtual"] then + -- add virtual vertices to input graph + for _, vertex in ipairs(embedding.vertices) do + if vertex.virtual then + vertex.inputvertex = createVertex(self, { + name = nil,--vertex.name, + generated_options = {}, + text = vertex.name + }) + vertex.virtual = false + end + end + + -- add virtual edges to input graph + for _, vertex in ipairs(embedding.vertices) do + for halfedge in Embedding.adjacency_iterator(vertex.link) do + if halfedge.virtual then + createEdge( + self, + vertex.inputvertex, + halfedge.target.inputvertex + ) + end + halfedge.virtual = false + end + end + end + + -- create canonical ordering + local order = embedding:canonical_order(v1, v2, vn) + + local sm = ShiftMethod.new() + sm:init(order) + local gridpos = sm:run() + + local gridspacing = options["grid spacing"] + for _, v in ipairs(order) do + if not v.virtual then + local iv = v.inputvertex + iv.pos.x = gridpos[v].x * gridspacing + iv.pos.y = gridpos[v].y * gridspacing + end + end + + embedding:remove_virtual() + + --start = os.clock() + if options["use pdp"] then + local pdp = PDP.new( + self.ugraph, embedding, + options["node distance"], + options["node distance"], + options["pdp cooling factor"], + options["exponent change iterations"], + options["start repulsive exponent"], + options["end repulsive exponent"], + options["start attractive exponent"], + options["end attractive exponent"], + options["edge approach threshold"], + options["edge stretch threshold"], + options["stress counter threshold"], + options["edge divisions"] + ) + pdp:run() + end + +end diff --git a/graphics/pgf/base/tex/generic/graphdrawing/lua/pgf/gd/planar/ShiftMethod.lua b/graphics/pgf/base/tex/generic/graphdrawing/lua/pgf/gd/planar/ShiftMethod.lua new file mode 100644 index 0000000000..c568ca5696 --- /dev/null +++ b/graphics/pgf/base/tex/generic/graphdrawing/lua/pgf/gd/planar/ShiftMethod.lua @@ -0,0 +1,128 @@ +local SM = {} +require("pgf.gd.planar").ShiftMethod = SM + +-- imports +local Embedding = require("pgf.gd.planar.Embedding") + +-- create class properties +SM.__index = SM + +function SM.new() + local t = {} + setmetatable(t, SM) + return t +end + +function SM:init(vertices) + self.vertices = vertices + self.xoff = {} + self.pos = {} + for _, v in ipairs(vertices) do + self.pos[v] = {} + end + self.left = {} + self.right = {} +end + +function SM:run() + local v1 = self.vertices[1] + local v2 = self.vertices[2] + local v3 = self.vertices[3] + + self.xoff[v1] = 0 + self.pos[v1].y = 0 + self.right[v1] = v3 + + self.xoff[v3] = 1 + self.pos[v3].y = 1 + self.right[v3] = v2 + + self.xoff[v2] = 1 + self.pos[v2].y = 0 + + local n = #self.vertices + for k = 4, n do + local vk = self.vertices[k] + local wplink, wqlink, wp1qsum + if k ~= n then + wplink, wqlink, wp1qsum = self:get_attachments(vk) + else + wplink, wqlink, wp1qsum = self:get_last_attachments(vk, v1, v2) + end + local wp, wq = wplink.target, wqlink.target + local wp1 = wplink.links[0].target + local wq1 = wqlink.links[1 - 0].target + self.xoff[wp1] = self.xoff[wp1] + 1 + self.xoff[wq] = self.xoff[wq] + 1 + wp1qsum = wp1qsum + 2 + self.xoff[vk] = (wp1qsum + self.pos[wq].y - self.pos[wp].y) / 2 + self.pos[vk].y = (wp1qsum + self.pos[wq].y + self.pos[wp].y) / 2 + -- = self.xoff[vk] + self.pos[wp].y ? + self.right[wp] = vk + if wp ~= wq1 then + self.left[vk] = wp1 + self.right[wq1] = nil + self.xoff[wp1] = self.xoff[wp1] - self.xoff[vk] + end + self.right[vk] = wq + self.xoff[wq] = wp1qsum - self.xoff[vk] + end + self.pos[v1].x = 0 + self:accumulate_offset(v1, 0) + return self.pos +end + +function SM:get_attachments(vk) + local wplink, wqlink + local wp1qsum = 0 + local start = vk.link + local startattach = self.xoff[start.target] ~= nil + local current = start.links[0] + local last = start + repeat + local currentattach = self.xoff[current.target] ~= nil + local lastattach = self.xoff[last.target] ~= nil + if currentattach ~= lastattach then + if currentattach then + wplink = current + else + wqlink = last + end + if currentattach == startattach and not startattach then + break + end + currentattach = lastattach + elseif currentattach then + wp1qsum = wp1qsum + self.xoff[current.target] + end + last = current + current = current.links[0] + until last == start + return wplink, wqlink, wp1qsum +end + +function SM:get_last_attachments(vn, v1, v2) + local wplink, wqlink + local wp1qsum = 0 + for halfedge in Embedding.adjacency_iterator(vn.link, ccwdir) do + local target = halfedge.target + if target == v1 then + wplink = halfedge + elseif target == v2 then + wqlink = halfedge + end + wp1qsum = wp1qsum + self.xoff[target] + end + return wplink, wqlink, wp1qsum +end + +function SM:accumulate_offset(v, x) + x = x + self.xoff[v] + self.pos[v].x = x + local l = self.left[v] + local r = self.right[v] + if l then self:accumulate_offset(l, x) end + if r then self:accumulate_offset(r, x) end +end + +return SM diff --git a/graphics/pgf/base/tex/generic/graphdrawing/lua/pgf/gd/planar/library.lua b/graphics/pgf/base/tex/generic/graphdrawing/lua/pgf/gd/planar/library.lua new file mode 100644 index 0000000000..68c46898e3 --- /dev/null +++ b/graphics/pgf/base/tex/generic/graphdrawing/lua/pgf/gd/planar/library.lua @@ -0,0 +1,2 @@ +require "pgf.gd.planar.PlanarLayout" +require "pgf.gd.planar.parameters" diff --git a/graphics/pgf/base/tex/generic/graphdrawing/lua/pgf/gd/planar/parameters.lua b/graphics/pgf/base/tex/generic/graphdrawing/lua/pgf/gd/planar/parameters.lua new file mode 100644 index 0000000000..603f2ee7cc --- /dev/null +++ b/graphics/pgf/base/tex/generic/graphdrawing/lua/pgf/gd/planar/parameters.lua @@ -0,0 +1,144 @@ +-- Imports +local declare = require("pgf.gd.interface.InterfaceToAlgorithms").declare + +declare { + key = "use pdp", + type = "boolean", + initial = "true", + + summary = [[" + Whether or not to use the Planar Drawing Postprocessing + to improve the drawing. + "]] +} + +declare { + key = "use sf", + type = "boolean", + initial = "true", + + summary = [[" + Whether or not to use the Sort and Flip Algorithm + to improve the combinatorical embedding. + "]] +} + +declare { + key = "grid spacing", + type = "number", + initial = "10", + + summary = [[" + If the |use pdp| option is not set, + this sets the spacing of the grid used by the shift method. + A bigger grid spacing will result in a bigger drawing. + "]] +} + +declare { + key = "pdp cooling factor", + type = "number", + initial = "0.98", + + summary = [[" + This sets the cooling factor used by the Planar Drawing Postprocessing. + A higher cooling factor can result in better quality of the drawing, + but will increase the run time of the algorithm. + "]] +} + +declare { + key = "start repulsive exponent", + type = "number", + initial = "2", + + summary = [[" + Start value of the exponent used in the calculation of all repulsive forces in PDP + "]] +} + +declare { + key = "end repulsive exponent", + type = "number", + initial = "2", + + summary = [[" + End value of the exponent used in the calculation of all repulsive forces in PDP. + "]] +} + +declare { + key = "start attractive exponent", + type = "number", + initial = "2", + + summary = [[" + Start value of the exponent used in PDP's calculation of the attractive force between + nodes connected by an edge. + "]] +} + +declare { + key = "end attractive exponent", + type = "number", + initial = "2", + + summary = [[" + End value of the exponent used in PDP's calculation of the attractive force between + nodes connected by an edge. + "]] +} + +declare { + key = "exponent change iterations", + type = "number", + initial = "1", + + summary = [[" + The number of iterations over which to modify the force exponents. + In iteration one the exponents will have their start value and in iteration + |exponent change iterations| they will have their end value. + "]] +} + +declare { + key = "edge approach threshold", + type = "number", + initial = "0.3", + + summary = [[" + The maximum ration between the actual and the desired node-edge distance + which is required to count an edge as stressed. + "]] +} + +declare { + key = "edge stretch threshold", + type = "number", + initial = "1.5", + + summary = [[" + The minimum ration between the actual and the desired edge length + which is required to count an edge as stressed. + "]] +} + +declare { + key = "stress counter threshold", + type = "number", + initial = "30", + + summary = [[" + The number of iterations an edge has to be under stress before it will be subdivided. + "]] +} + +declare { + key = "edge divisions", + type = "number", + initial = "0", + + summary = [[" + The number of edges in which stressed edges will be subdivided. + "]] +} diff --git a/graphics/pgf/base/tex/generic/graphdrawing/lua/pgf/gd/routing.lua b/graphics/pgf/base/tex/generic/graphdrawing/lua/pgf/gd/routing.lua new file mode 100644 index 0000000000..77166c5cbf --- /dev/null +++ b/graphics/pgf/base/tex/generic/graphdrawing/lua/pgf/gd/routing.lua @@ -0,0 +1,22 @@ +-- Copyright 2012 by Till Tantau +-- +-- This file may be distributed an/or modified +-- +-- 1. under the LaTeX Project Public License and/or +-- 2. under the GNU Public License +-- +-- See the file doc/generic/pgf/licenses/LICENSE for more information + +--- @release $Header$ + + + +local routing = {} + +-- Declare namespace +require("pgf.gd").routing = routing + + +-- Done + +return routing
\ No newline at end of file diff --git a/graphics/pgf/base/tex/generic/graphdrawing/lua/pgf/gd/routing/Hints.lua b/graphics/pgf/base/tex/generic/graphdrawing/lua/pgf/gd/routing/Hints.lua new file mode 100644 index 0000000000..7d34726f47 --- /dev/null +++ b/graphics/pgf/base/tex/generic/graphdrawing/lua/pgf/gd/routing/Hints.lua @@ -0,0 +1,100 @@ +-- Copyright 2014 by Till Tantau +-- +-- This file may be distributed an/or modified +-- +-- 1. under the LaTeX Project Public License and/or +-- 2. under the GNU Public License +-- +-- See the file doc/generic/pgf/licenses/LICENSE for more information + +-- @release $Header$ + + +--- +-- The |Hints| class provides a way for graph drawing algorithms to +-- communicate certain possibilities concerning the routing of edges +-- to edge routing algorithms. This partly decouples the choice of the +-- vertex positioning algorithms from the choice of edge routing +-- algorithm. For instance, for a simple necklace routing, it is +-- unclear whether the edges on the necklace should be routing ``along +-- the necklace'' or not. Thus, necklace routing algorithms will +-- ``hint'' that a necklace is present and only when the +-- |necklace routing| algorithm is selected will these hints lead to +-- actual bending of edges. +-- +-- For each kind of hint, there are methods in this class for creating +-- the hints and other methods for reading them. Hints are always +-- local to the ugraph. + +local Hints = {} + +-- Namespace +require("pgf.gd.routing").Hints = Hints + +-- Imports +local Storage = require("pgf.gd.lib.Storage") +local Coordinate = require("pgf.gd.model.Coordinate") + + + + +-- The necklace storage + +local necklaces = Storage.new() + + +--- +-- Adds a necklace hint. In this case, the hint indicates that the +-- given sequence of vertices lie on a circle. +-- +-- The idea is that an algorithm may specify that in a +-- given graph certain sequences of nodes form a ``necklace'', which +-- is typically a circle. There may be more than one necklace inside a +-- given graph. For each necklace, +-- whenever an arc connects subsequent nodes on the necklace, they get +-- bend in such a way that they lie follow the path of the +-- necklace. If an arc lies on more than one necklace, the ``last one +-- wins''. +-- +-- @param ugraph The ugraph to which this hint is added +-- @param necklace The sequence of vertices that form the necklace. If +-- the necklace is closed, the last vertex must equal the first one. +-- @param center If provided, must be |Coordinate| that specifies the +-- center of the circle on which the vertices lie. If not provided, +-- the origin is assumed. +-- @param clockwise If |true|, the vertices are in clockwise order, +-- otherwise in counter-clockwise order. + +function Hints.addNecklaceCircleHint(ugraph, necklace, center, clockwise) + local a = necklaces[ugraph] or {} + necklaces[ugraph] = a + + a[#a+1] = { + necklace = necklace, + center = center or Coordinate.origin, + clockwise = clockwise + } +end + + +--- +-- Gets the necklace hints. +-- +-- This function will return an array whose entries are necklace +-- hints. Each entry in the array has a |necklace| field, which is the +-- field passed to the |addNecklaceXxxx| methods. For a circle +-- necklace, the |center| and |clockwise| fields will be set. (Other +-- necklaces are not yet implemented.) +-- +-- @param ugraph The ugraph for which the necklace hints are +-- requested. +-- @return The array of necklaces as described above. + +function Hints.getNecklaceHints(ugraph) + return necklaces[ugraph] or {} +end + +-- done + +return Hints + diff --git a/graphics/pgf/base/tex/generic/graphdrawing/lua/pgf/gd/routing/NecklaceRouting.lua b/graphics/pgf/base/tex/generic/graphdrawing/lua/pgf/gd/routing/NecklaceRouting.lua new file mode 100644 index 0000000000..defcf9f8c6 --- /dev/null +++ b/graphics/pgf/base/tex/generic/graphdrawing/lua/pgf/gd/routing/NecklaceRouting.lua @@ -0,0 +1,90 @@ +-- Copyright 2014 by Till Tantau +-- +-- This file may be distributed an/or modified +-- +-- 1. under the LaTeX Project Public License and/or +-- 2. under the GNU Public License +-- +-- See the file doc/generic/pgf/licenses/LICENSE for more information + +-- @release $Header$ + + +-- The class; it processes necklace hints. + +local NecklaceRouting = {} + + +-- Namespace +require("pgf.gd.routing").NecklaceRouting = NecklaceRouting + +-- Imports +local declare = require("pgf.gd.interface.InterfaceToAlgorithms").declare + +local Hints = require "pgf.gd.routing.Hints" +local Path = require "pgf.gd.model.Path" + + +--- +declare { + key = "necklace routing", + algorithm = NecklaceRouting, + + phase = "edge routing", + + summary = "Bends all edges of a graph that lie on ``necklaces'' along these necklaces.", + + documentation = [[" + Some graph drawing algorithms lay out some or all nodes along a + path, which is then called a \emph{necklace}. For instance, the + |simple necklace layout| places all nodes on a circle and that + circle is the ``necklace''. When the |necklace routing| edge + routing algorithm is selected, all edges that connect subsequent + nodes on such a necklace are bend in such a way that the + ``follow the necklace path''. In the example case, this will + cause all edges that connect adjacent nodes to become arcs on + of the circle on which the nodes lie. + + Note that local edge routing options for an edge may overrule + the edge routing computed by the algorithm as in the edge from 6 + to 7 in the example. + "]], + + examples = [[" + \tikz \graph [simple necklace layout, node distance=1.5cm, + necklace routing, + nodes={draw,circle}, edges={>={Stealth[round,sep,bend]}}] + { 1 -> 2 [minimum size=30pt] <- 3 <-> 4 -- + 5 -- 6 -- [bend left] 7 -- 1 -- 4 }; + "]] +} + + + +-- The implementation + +function NecklaceRouting:run() + local ugraph = self.ugraph + + for _,entry in ipairs(Hints.getNecklaceHints(ugraph)) do + assert (entry.center) -- no other necklace types, yet + local prev + for _,vertex in ipairs(entry.necklace) do + if prev then + local a = ugraph:arc(prev, vertex) + if a then + local p = Path.new() + p:appendMoveto(a.tail.pos:clone()) + p:appendArcTo(a.head.pos:clone(), entry.center, entry.clockwise) + a.path = p + end + end + prev = vertex + end + end +end + + +-- done + +return NecklaceRouting diff --git a/graphics/pgf/base/tex/generic/graphdrawing/lua/pgf/gd/routing/library.lua b/graphics/pgf/base/tex/generic/graphdrawing/lua/pgf/gd/routing/library.lua new file mode 100644 index 0000000000..17c959d685 --- /dev/null +++ b/graphics/pgf/base/tex/generic/graphdrawing/lua/pgf/gd/routing/library.lua @@ -0,0 +1,29 @@ +-- Copyright 2012 by Till Tantau +-- +-- This file may be distributed an/or modified +-- +-- 1. under the LaTeX Project Public License and/or +-- 2. under the GNU Public License +-- +-- See the file doc/generic/pgf/licenses/LICENSE for more information + +-- @release $Header$ + + +-- Imports +local declare = require "pgf.gd.interface.InterfaceToAlgorithms".declare + +--- +-- This library contains algorithms for routing edges through a graph. +-- +-- @library + +local routing -- Library name + +-- Load declarations from: + +-- Load algorithms from: +require "pgf.gd.routing.NecklaceRouting" + + +-- General declarations diff --git a/graphics/pgf/base/tex/generic/graphdrawing/lua/pgf/gd/tools/make_gd_wrap.lua b/graphics/pgf/base/tex/generic/graphdrawing/lua/pgf/gd/tools/make_gd_wrap.lua new file mode 100644 index 0000000000..a0aee6501c --- /dev/null +++ b/graphics/pgf/base/tex/generic/graphdrawing/lua/pgf/gd/tools/make_gd_wrap.lua @@ -0,0 +1,183 @@ +-- Copyright 2012 by Till Tantau +-- +-- This file may be distributed an/or modified +-- +-- 1. under the LaTeX Project Public License and/or +-- 2. under the GNU Public License +-- +-- See the file doc/generic/pgf/licenses/LICENSE for more information + +-- @release $Header$ + + + +--- +-- This program generates a C wrap file around graph drawing +-- algorithms. The idea is that when you have a graph drawing +-- algorithm implemented in C and wish to invoke it from Lua, you need +-- a wrapper that manages the translation between Lua and C. This +-- program is intended to make it (reasonably) easy to produce such a +-- wrapper. + + + +-- Sufficient number of arguments? + +if #arg < 4 or arg[1] == "-h" or arg[1] == "-?" or arg[1] == "--help" then + print([[" +Usage: make_gd_wrap library1 library2 ... libraryn template library_name target_c_file + +This program will read all of the graph drawing library files using +Lua's require. Then, it will iterate over all declared algorithm keys +(declared using declare { algorithm_written_in_c = ... }) and will +produce the code for library for the required target C files based on +the template. +"]]) + os.exit() +end + + +-- Imports + +local InterfaceToDisplay = require "pgf.gd.interface.InterfaceToDisplay" +local InterfaceCore = require "pgf.gd.interface.InterfaceCore" + + +-- Ok, setup: + +InterfaceToDisplay.bind(require "pgf.gd.bindings.Binding") + + +-- Now, read all libraries: + +for i=1,#arg-3 do + require(arg[i]) +end + + +-- Now, read the template: + +local file = io.open(arg[#arg-2]) +local template = file:read("*a") +file:close() + +-- Let us grab the declaration: + +local functions_dec = (template:match("%$functions(%b{})") or ""):match("^{(.*)}$") +local functions_reg_dec = (template:match("%$functions_registry(%b{})") or ""):match("^{(.*)}$") +local factories_dec = (template:match("%$factories(%b{})") or ""):match("^{(.*)}$") +local factories_reg_dec = (template:match("%$factories_registry(%b{})") or ""):match("^{(.*)}$") + +-- Now, handle all keys with a algorithm_written_in_c field + +local keys = InterfaceCore.keys +local filename = arg[#arg] +local target = arg[#arg-1] + +local includes = {} +local functions = {} +local functions_registry = {} + +local factories = {} +local factories_reg = {} + +for _,k in ipairs(keys) do + + if k.algorithm_written_in_c and k.code then + + local library, fun_name = k.algorithm_written_in_c:match("(.*)%.(.*)") + + if target == library then + -- First, gather the includes: + if type(k.includes) == "string" then + if not includes[k.includes] then + includes[#includes + 1] = k.includes + includes[k.includes] = true + end + elseif type(k.includes) == "table" then + for _,i in ipairs(k.includes) do + if not includes[i] then + includes[#includes + 1] = i + includes[i] = true + end + end + end + + -- Second, create a code block: + functions[#functions+1] = functions_dec:gsub("%$([%w_]-)%b{}", + { + function_name = fun_name, + function_body = k.code + }) + + -- Third, create functions_registry entry + functions_registry[#functions_registry + 1] = functions_reg_dec:gsub("%$([%w_]-)%b{}", + { + function_name = fun_name, + function_body = k.code + }) + end + end + + + if k.module_class then + + -- First, gather the includes: + if type(k.includes) == "string" then + if not includes[k.includes] then + includes[#includes + 1] = k.includes + includes[k.includes] = true + end + elseif type(k.includes) == "table" then + for _,i in ipairs(k.includes) do + if not includes[i] then + includes[#includes + 1] = i + includes[i] = true + end + end + end + + -- Second, create a code block: + factories[#factories+1] = factories_dec:gsub( + "%$([%w_]-)%b{}", + { + factory_class = k.module_class, + factory_code = k.code, + factory_base = k.module_base, + factory_name = k.module_class .. '_factory' + }) + + -- Third, create factories_registry entry + factories_reg[#factories_reg + 1] = factories_reg_dec:gsub( + "%$([%w_]-)%b{}", + { + factory_class = k.module_class, + factory_code = k.code, + factory_base = k.module_base, + factory_name = k.module_class .. '_factory' + }) + end +end + + +local file = io.open(filename, "w") + +if not file then + print ("failed to open file " .. filename) + os.exit(-1) +end + +file:write ((template:gsub( + "%$([%w_]-)%b{}", + { + factories = table.concat(factories, "\n\n"), + factories_registry = table.concat(factories_reg, "\n"), + functions = table.concat(functions, "\n\n"), + functions_registry = table.concat(functions_registry, "\n"), + includes = table.concat(includes, "\n"), + library_c_name = target:gsub("%.", "_"), + library_name = target + }))) +file:close() + + diff --git a/graphics/pgf/base/tex/generic/graphdrawing/lua/pgf/gd/trees.lua b/graphics/pgf/base/tex/generic/graphdrawing/lua/pgf/gd/trees.lua new file mode 100644 index 0000000000..8c45e912dc --- /dev/null +++ b/graphics/pgf/base/tex/generic/graphdrawing/lua/pgf/gd/trees.lua @@ -0,0 +1,21 @@ +-- Copyright 2012 by Till Tantau +-- +-- This file may be distributed an/or modified +-- +-- 1. under the LaTeX Project Public License and/or +-- 2. under the GNU Public License +-- +-- See the file doc/generic/pgf/licenses/LICENSE for more information + +--- @release $Header$ + + +local trees = {} + +-- Declare namespace +require("pgf.gd").trees = tree + + +-- Done + +return trees
\ No newline at end of file diff --git a/graphics/pgf/base/tex/generic/graphdrawing/lua/pgf/gd/trees/ChildSpec.lua b/graphics/pgf/base/tex/generic/graphdrawing/lua/pgf/gd/trees/ChildSpec.lua new file mode 100644 index 0000000000..cc3c18d12c --- /dev/null +++ b/graphics/pgf/base/tex/generic/graphdrawing/lua/pgf/gd/trees/ChildSpec.lua @@ -0,0 +1,226 @@ +-- Copyright 2012 by Till Tantau +-- +-- This file may be distributed an/or modified +-- +-- 1. under the LaTeX Project Public License and/or +-- 2. under the GNU Public License +-- +-- See the file doc/generic/pgf/licenses/LICENSE for more information + +-- @release $Header$ + + + +--- +-- @section subsection {Specifying Missing Children} +-- \label{section-gd-missing-children} +-- +-- In the present section we discuss keys for specifying missing children +-- in a tree. For many certain kind of trees, in particular for binary +-- trees, there are not just ``a certain number of children'' at each +-- node, but, rather, there is a designated ``first'' (or ``left'') child +-- and a ``second'' (or ``right'') child. Even if one of these children +-- is missing and a node actually has only one child, the single child will +-- still be a ``first'' or ``second'' child and this information should +-- be taken into consideration when drawing a tree. +-- +-- The first useful key for specifying missing children is +-- |missing number of children| which allows you to state how many +-- children there are, at minimum. +-- +-- Once the minimum number of children has been set, we still need a way +-- of specifying ``missing first children'' or, more generally, missing +-- children that are not ``at the end'' of the list of children. For +-- this, there are three methods: +-- % +-- \begin{enumerate} +-- \item When you use the |child| syntax, you can use the |missing| key +-- with the |child| command to indicate a missing child: +-- % +-- \begin{codeexample}[preamble={\usetikzlibrary{graphs,graphdrawing} +-- \usegdlibrary{trees}}] +-- \tikz [binary tree layout, level distance=5mm] +-- \node {a} +-- child { node {b} +-- child { node {c} +-- child { node {d} } +-- } } +-- child { node {e} +-- child [missing] +-- child { node {f} +-- child [missing] +-- child { node {g} +-- } } }; +-- \end{codeexample} +-- % +-- \item When using the |graph| syntax, you can use an ``empty node'', +-- which really must be completely empty and may not even contain a +-- slash, to indicate a missing node: +-- % +-- \begin{codeexample}[preamble={\usetikzlibrary{graphs,graphdrawing} +-- \usegdlibrary{trees}}] +-- \tikz [binary tree layout, level distance=5mm] +-- \graph { a -> { b -> c -> d, e -> { , f -> { , g} } } }; +-- \end{codeexample} +-- % +-- \item You can simply specify the index of a child directly using +-- the key |desired child index|. +-- \end{enumerate} +-- +-- @end + + +-- Imports +local declare = require("pgf.gd.interface.InterfaceToAlgorithms").declare + + + +--- +-- +declare { + key = "minimum number of children", + type = "number", + initial = "0", + + summary = [[" + Specifies how many children a tree node must have at least. If + there are less, ``virtual'' children are added. + "]], + documentation = [[" + When this key is set to |2| or more, the following happens: We first + compute a spanning tree for the graph, see + Section~\ref{subsection-gd-spanning-tree}. Then, whenever a node is + not a leaf in this spanning tree (when it has at least one child), + we add ``virtual'' or ``dummy'' nodes as children of the node until + the total number of real and dummy children is at least + \meta{number}. If there where at least \meta{number} children at the + beginning, nothing happens. + + The new children are added after the existing children. This means + that, for instance, in a tree with \meta{number} set to |2|, for + every node with a single child, this child will be the first child + and the second child will be missing. + "]], + examples = [[" + \tikz \graph [binary tree layout,level distance=5mm] + { a -> { b->c->d, e->f->g } }; + "]] +} + +--- + +declare { + key = "desired child index", + type = "number", + + summary = [[" + Pass this key to a node to tell the graph drawing engine which child + number you ``desired'' for the node. Whenever all desires for the + children of a node are conflict-free, they will all be met; children + for which no desired indices were given will remain at their + position, whenever possible, but will ``make way'' for children with + a desired position. + "]], + documentation = [[" + In detail, the following happens: We first + determine the total number of children (real or dummy) needed, which + is the maximum of the actual number of children, of the + \texttt{minimum number of children}, and of the highest desired + child index. Then we go over all children that have a desired child + index and put they at this position. If the position is already + taken (because some other child had the same desired index), the + next free position is used with a wrap-around occurring at the + end. Next, all children without a desired index are place using the + same mechanism, but they want to be placed at the position they had + in the original spanning tree. + + While all of this might sound a bit complicated, the application of + the key in a binary tree is pretty straightforward: To indicate that + a node is a ``right'' child in a tree, just add \texttt{desired child index=2} + to it. This will make it a second child, possibly causing the first + child to be missing. If there are two nodes specified as children of + a node, by saying \texttt{desired child index=}\meta{number} for one + of them, you will cause it be first or second child, depending on + \meta{number}, and cause the \emph{other} child to become the other + child. + + Since |desired child index=2| is a bit long, the following shortcuts + are available: |first|, |second|, |third|, and |fourth|. + You might wonder why |second| is used rather than |right|. The + reason is that trees may also grow left and right and, additionally, + the |right| and |left| keys are already in use for + anchoring. Naturally, you can locally redefine them, if you want. + "]], + examples = {[[" + \tikz \graph [binary tree layout, level distance=5mm] + { a -> b[second] }; + "]],[[" + \tikz \graph [binary tree layout, level distance=5mm] + { a -> { b[second], c} }; + "]],[[" + \tikz \graph [binary tree layout, level distance=5mm] + { a -> { b, c[first]} }; + "]],[[" + \tikz \graph [binary tree layout, level distance=5mm] + { a -> { b[second], c[second]} }; + "]],[[" + \tikz \graph [binary tree layout, level distance=5mm] + { a -> { b[third], c[first], d} }; + "]] + } +} + + +--- + +declare { + key = "first", + use = { + { key = "desired child index", value = 1}, + }, + + summary = [[" + A shorthand for setting the desired child number to |1|. + "]] + } + +--- + +declare { + key = "second", + use = { + { key = "desired child index", value = 2}, + }, + + summary = [[" + A shorthand for setting the desired child number to |2|. + "]] + } + + +--- + +declare { + key = "third", + use = { + { key = "desired child index", value = 3}, + }, + + summary = [[" + A shorthand for setting the desired child number to |3|. + "]] + } + + +--- + +declare { + key = "fourth", + use = { + { key = "desired child index", value = 4} + }, + + summary = [[" + A shorthand for setting the desired child number to |4|. + "]] + } diff --git a/graphics/pgf/base/tex/generic/graphdrawing/lua/pgf/gd/trees/ReingoldTilford1981.lua b/graphics/pgf/base/tex/generic/graphdrawing/lua/pgf/gd/trees/ReingoldTilford1981.lua new file mode 100644 index 0000000000..69babd443c --- /dev/null +++ b/graphics/pgf/base/tex/generic/graphdrawing/lua/pgf/gd/trees/ReingoldTilford1981.lua @@ -0,0 +1,211 @@ +-- Copyright 2012 by Till Tantau +-- +-- This file may be distributed an/or modified +-- +-- 1. under the LaTeX Project Public License and/or +-- 2. under the GNU Public License +-- +-- See the file doc/generic/pgf/licenses/LICENSE for more information + +-- @release $Header$ + + + +--- +-- @section subsubsection {The Reingold--Tilford Layout} +-- +-- @end + +local ReingoldTilford1981 = {} + +-- Imports +local layered = require "pgf.gd.layered" +local declare = require("pgf.gd.interface.InterfaceToAlgorithms").declare +local Storage = require "pgf.gd.lib.Storage" + +--- +declare { + key = "tree layout", + algorithm = ReingoldTilford1981, + + preconditions = { + connected = true, + tree = true + }, + + postconditions = { + upward_oriented = true + }, + + documentation_in = "pgf.gd.trees.doc" +} + + +--- +declare { + key = "missing nodes get space", + type = "boolean", + documentation_in = "pgf.gd.trees.doc" +} + + + +--- +declare { + key = "significant sep", + type = "length", + initial = "0", + documentation_in = "pgf.gd.trees.doc" +} + + +--- +declare { + key = "binary tree layout", + use = { + { key = "tree layout" }, + { key = "minimum number of children" , value=2 }, + { key = "significant sep", value = 10 }, + }, + documentation_in = "pgf.gd.trees.doc" +} + +--- +declare { + key = "extended binary tree layout", + use = { + { key = "tree layout" }, + { key = "minimum number of children" , value=2 }, + { key = "missing nodes get space" }, + { key = "significant sep", value = 0 }, + }, + documentation_in = "pgf.gd.trees.doc" +} + + + + +-- Now comes the implementation: + +function ReingoldTilford1981:run() + + local root = self.spanning_tree.root + + local layers = Storage.new() + local descendants = Storage.new() + + self.extended_version = self.digraph.options['missing nodes get space'] + + self:precomputeDescendants(root, 1, layers, descendants) + self:computeHorizontalPosition(root, layers, descendants) + layered.arrange_layers_by_baselines(layers, self.adjusted_bb, self.ugraph) + +end + + +function ReingoldTilford1981:precomputeDescendants(node, depth, layers, descendants) + local my_descendants = { node } + + for _,arc in ipairs(self.spanning_tree:outgoing(node)) do + local head = arc.head + self:precomputeDescendants(head, depth+1, layers, descendants) + for _,d in ipairs(descendants[head]) do + my_descendants[#my_descendants + 1] = d + end + end + + layers[node] = depth + descendants[node] = my_descendants +end + + + +function ReingoldTilford1981:computeHorizontalPosition(node, layers, descendants) + + local children = self.spanning_tree:outgoing(node) + + node.pos.x = 0 + + local child_depth = layers[node] + 1 + + if #children > 0 then + -- First, compute positions for all children: + for i=1,#children do + self:computeHorizontalPosition(children[i].head, layers, descendants) + end + + -- Now, compute minimum distances and shift them + local right_borders = {} + + for i=1,#children-1 do + + local local_right_borders = {} + + -- Advance "right border" of the subtree rooted at + -- the i-th child + for _,d in ipairs(descendants[children[i].head]) do + local layer = layers[d] + local x = d.pos.x + if self.extended_version or not (layer > child_depth and d.kind == "dummy") then + if not right_borders[layer] or right_borders[layer].pos.x < x then + right_borders[layer] = d + end + if not local_right_borders[layer] or local_right_borders[layer].pos.x < x then + local_right_borders[layer] = d + end + end + end + + local left_borders = {} + -- Now left for i+1 st child + for _,d in ipairs(descendants[children[i+1].head]) do + local layer = layers[d] + local x = d.pos.x + if self.extended_version or not (layer > child_depth and d.kind == "dummy") then + if not left_borders[layer] or left_borders[layer].pos.x > x then + left_borders[layer] = d + end + end + end + + -- Now walk down the lines and try to find out what the minimum + -- distance needs to be. + + local shift = -math.huge + local first_dist = left_borders[child_depth].pos.x - local_right_borders[child_depth].pos.x + local is_significant = false + + for layer,n2 in pairs(left_borders) do + local n1 = right_borders[layer] + if n1 then + shift = math.max( + shift, + layered.ideal_sibling_distance(self.adjusted_bb, self.ugraph, n1, n2) + n1.pos.x - n2.pos.x + ) + end + if local_right_borders[layer] then + if layer > child_depth and + (left_borders[layer].pos.x - local_right_borders[layer].pos.x <= first_dist) then + is_significant = true + end + end + end + + if is_significant then + shift = shift + self.ugraph.options['significant sep'] + end + + -- Shift all nodes in the subtree by shift: + for _,d in ipairs(descendants[children[i+1].head]) do + d.pos.x = d.pos.x + shift + end + end + + -- Finally, position root in the middle: + node.pos.x = (children[1].head.pos.x + children[#children].head.pos.x) / 2 + end +end + + + +return ReingoldTilford1981
\ No newline at end of file diff --git a/graphics/pgf/base/tex/generic/graphdrawing/lua/pgf/gd/trees/SpanningTreeComputation.lua b/graphics/pgf/base/tex/generic/graphdrawing/lua/pgf/gd/trees/SpanningTreeComputation.lua new file mode 100644 index 0000000000..dacf634865 --- /dev/null +++ b/graphics/pgf/base/tex/generic/graphdrawing/lua/pgf/gd/trees/SpanningTreeComputation.lua @@ -0,0 +1,646 @@ +-- Copyright 2012 by Till Tantau +-- +-- This file may be distributed an/or modified +-- +-- 1. under the LaTeX Project Public License and/or +-- 2. under the GNU Public License +-- +-- See the file doc/generic/pgf/licenses/LICENSE for more information + +-- @release $Header$ + + + +--- +-- @section subsection {Spanning Tree Computation} +-- +-- \label{subsection-gd-spanning-tree} +-- Although the algorithms of this library are tailored to layout trees, +-- they will work for any graph as input. First, if the graph is not +-- connected, it is decomposed into connected components and these are +-- laid out individually. Second, for each component, a spanning tree of +-- the graph is computed first and the layout is computed for this +-- spanning tree; all other edges will still be drawn, but they have no +-- impact on the placement of the nodes. If the graph is already a tree, +-- the spanning tree will be the original graph. +-- +-- The computation of the spanning tree is a non-trivial process since +-- a non-tree graph has many different possible spanning trees. You can +-- choose between different methods for deciding on a spanning tree, it +-- is even possible to implement new algorithms. (In the future, the +-- computation of spanning trees and the cycle removal in layered graph +-- drawing algorithms will be unified, but, currently, they are +-- implemented differently.) +-- +-- Selects the (sub)algorithm that is to be used for computing spanning +-- trees whenever this is requested by a tree layout algorithm. The +-- default algorithm is |breadth first spanning tree|. +--% +--\begin{codeexample}[preamble={\usetikzlibrary{graphs,graphdrawing} +-- \usegdlibrary{trees}}] +--\tikz \graph [tree layout, breadth first spanning tree] +--{ +-- 1 -- {2,3,4,5} -- 6; +--}; +--\end{codeexample} +--% +--\begin{codeexample}[preamble={\usetikzlibrary{graphs,graphdrawing} +-- \usegdlibrary{trees}}] +--\tikz \graph [tree layout, depth first spanning tree] +--{ +-- 1 --[bend right] {2,3,4,5 [>bend left]} -- 6; +--}; +--\end{codeexample} +-- +-- @end + +local SpanningTreeComputation = {} + + + +-- Namespace +require("pgf.gd.trees").SpanningTreeComputation = SpanningTreeComputation + + +-- Imports +local lib = require "pgf.gd.lib" + +local Vertex = require "pgf.gd.model.Vertex" +local Digraph = require "pgf.gd.model.Digraph" + + +local declare = require("pgf.gd.interface.InterfaceToAlgorithms").declare + + + + +-- ------------------------- -- +-- General tree parameters -- +-- ------------------------- -- + + + + +--- +-- +declare { + key = "breadth first spanning tree", + algorithm = { + run = + function (self) + return SpanningTreeComputation.computeSpanningTree(self.ugraph, false, self.events) + end + }, + phase = "spanning tree computation", + phase_default = true, + + summary = [[" + This key selects ``breadth first'' as the (sub)algorithm for + computing spanning trees. Note that this key does not cause a graph + drawing scope to start; the key only has an effect in conjunction + with keys like |tree layout|. +"]], + documentation = [[" + The algorithm will be called whenever a graph drawing algorithm + needs a spanning tree on which to operate. It works as follows: + % + \begin{enumerate} + \item It looks for a node for which the |root| parameter is + set. If there are several such nodes, the first one is used. + If there are no such nodes, the first node is used. + + Let call the node determined in this way the \emph{root node}. + \item For every edge, a \emph{priority} is determined, which is a + number between 1 and 10. How this happens, exactly, will be + explained in a moment. Priority 1 means ``most important'' while + priority 10 means ``least important''. + \item Starting from the root node, we now perform a breadth first + search through the tree, thereby implicitly building a spanning + tree: Suppose for a moment that all edges have priority~1. Then, + the algorithm works just the way that a normal breadth first + search is performed: We keep a queue of to-be-visited nodes and + while this queue is not empty, we remove its first node. If this + node has not yet been visited, we add all its neighbors at the + end of the queue. When a node is taken out of the queue, we make + it the child of the node whose neighbor it was when it was + added. Since the queue follows the ``first in, first out'' + principle (it is a fifo queue), the children of the root will be + all nodes at distance $1$ form the root, their children will be + all nodes at distance $2$, and so on. + \item Now suppose that some edges have a priority different + from~1, in which case things get more complicated. We now keep + track of one fifo queue for each of the ten possible + priorities. When we consider the neighbors of a node, we actually + consider all its incident edges. Each of these edges has a certain + priority and the neighbor is put into the queue of the edge's + priority. Now, we still remove nodes normally from the queue for + priority~1; only if this queue is empty and there is still a node + in the queue for priority~2 we remove the first element from this + queue (and proceed as before). If the second queue is also empty, + we try the third, and so on up to the tenth queue. If all queues + are empty, the algorithm stops. + \end{enumerate} + + The effect of the ten queues is the following: If the edges of + priority $1$ span the whole graph, a spanning tree consisting solely + of these edges will be computed. However, if they do not, once we + have visited reachable using only priority 1 edges, we will extend + the spanning tree using a priority 2 edge; but then we once switch + back to using only priority 1 edges. If neither priority~1 nor + priority~2 edges suffice to cover the whole graph, priority~3 edges + are used, and so on. + "]] +} + +--- + +declare { + key = "depth first spanning tree", + algorithm = { + run = + function (self) + return SpanningTreeComputation.computeSpanningTree(self.ugraph, true, self.events) + end + }, + phase = "spanning tree computation", + + summary = [[" + Works exactly like |breadth first spanning tree| (same handling of + priorities), only the queues are now lifo instead of + fifo. + "]] +} + +--- +-- +declare { + key = "root", + type = "boolean", + default = true, + + summary = [[" + This Boolean parameter is used in the computation of spanning + trees. When can be set for a node, this node will be used as the + root for the spanning tree computation. If several nodes have this + option set, the first node will be used. + "]] +} + + +--- +-- +declare { + key = "span priority", + type = "number", + + summary = [[" + Explicitly sets the ``span priority'' of an edge to \meta{number}, which must be + a number between |1| and |10|. The priority of edges is used by + spanning tree computations, see |breadth first spanning tree|. + "]] +} + + + +--- +-- when it comes to choosing which edges are part of the spanning tree. +declare { + key = "span edge", + use = { + { key = "span priority", value = 1 }, + }, + + summary = [[" + An easy-to-remember shorthand for |span priority=1|. When this key + is used with an edge, it will always be preferred over other edges + "]] +} + + + + +--- +-- +declare { + key = "no span edge", + use = { + { key = "span priority", value = 10 }, + }, + + summary = [[" + An easy-to-remember shorthand for |span priority=10|. This causes + the edge to be used only as a last resort as part of a spanning + tree. + "]], + documentation = [[" + In the example, we add lots of edges that would normally be + preferred in the computation of the spanning tree, but use + |no span edge| to cause the algorithm to ignore these edges. + "]], + examples = [[" + \tikz \graph [tree layout, nodes={draw}, sibling distance=0pt, + every group/.style={ + default edge kind=->, no span edge, + path=source}] + { + 5 -> { + "1,3" -> {0,2,4}, + 11 -> { + "7,9" -> { 6, 8, 10 } + } + } + }; + "]] +} + + + +--- +declare { + key = "span priority ->", + type = "number", + initial = "3", + + summary = [[" + This key stores the span priority of all edges whose direction is + |->|. There are similar keys for all other directions, such as + |span priority <-| and so on. + "]], + documentation = [[" + When you write + % +\begin{codeexample}[code only] +graph { a -> b -- c <- [span priority=2] d } +\end{codeexample} + % + the priority of the edge from |a| to |b| would be the current + value of the key |span priority ->|, the priority of the edge from + |b| to |c| would be the current value of |span priority --|, and + the priority of the edge from |c| to |d| would be |2|, regardless + of the value of |span priority <-|. + + The defaults for the priorities are: + % + \begin{itemize} + \item |span priority -> = 3| + \item |span priority -- = 5| + \item |span priority <-> = 5| + \item |span priority <- = 8| + \item |span priority -!- = 10| + \end{itemize} + "]] +} + + + +--- + +declare { + key = "span priority reversed ->", + type = "number", + initial = "9", + + documentation = [[" + This key stores the span priority of traveling across reversed + edges whose actual direction is |->| (again, there are similar keys + for all other directions). + "]], + documentation = [[" + When you write + % +\begin{codeexample}[code only] +graph { a -> b -- c <- [span priority=2] d } +\end{codeexample} + % + there are, in addition to the priorities indicated above, also + further edge priorities: The priority of the (reversed) edge |b| + to |a| is |span priority reversed ->|, the priority of the + (reversed) edge |c| to |b| is |span priority reversed --|, and the + span priority of the reversed edge |d| to |c| is |2|, regardless + of the value of |span priority reversed <-|. + + The defaults for the priorities are: + % + \begin{itemize} + \item |span priority reversed -> = 9| + \item |span priority reversed -- = 5| + \item |span priority reversed <-> = 5| + \item |span priority reversed <- = 7| + \item |span priority reversed -!- = 10| + \end{itemize} + + The default priorities are set in such a way, that non-reversed |->| + edges have top priorities, |--| and |<->| edges have the same + priorities in either direction, and |<-| edges have low priority in + either direction (but going |a <- b| from |b| to |a| is given higher + priority than going from |a| to |b| via this edge and also higher + priority than going from |b| to |a| in |a -> b|). + + Keys like |span using directed| change the priorities ``en bloc''. + "]] +} + + +declare { + key = "span priority <-", + type = "number", + initial = "8", +} + +declare { + key = "span priority reversed <-", + type = "number", + initial = "7", +} + +declare { + key = "span priority --", + type = "number", + initial = "5", +} + +declare { + key = "span priority reversed --", + type = "number", + initial = "5", +} + +declare { + key = "span priority <->", + type = "number", + initial = "5", +} + +declare { + key = "span priority reversed <->", + type = "number", + initial = "5", +} + +declare { + key = "span priority -!-", + type = "number", + initial= "10", +} + +declare { + key = "span priority reversed -!-", + type = "number", + initial= "10", +} + +--- + +declare { + key = "span using directed", + use = { + { key = "span priority reversed <-", value = 3}, + { key = "span priority <->", value = 3}, + { key = "span priority reversed <->", value = 3}, + }, + summary = [[" + This style sets a priority of |3| for all edges that are directed + and ``go along the arrow direction'', that is, we go from |a| to + |b| with a priority of |3| for the cases |a -> b|, |b <- a|, + |a <-> b|, and |b <-> a|. + This strategy is nice with trees specified with both forward and + backward edges. + "]], + examples = [[" + \tikz \graph [tree layout, nodes={draw}, sibling distance=0pt, + span using directed] + { + 3 <- 5[root] -> 8, + 1 <- 3 -> 4, + 7 <- 8 -> 9, + 1 -- 4 -- 7 -- 9 + }; + "]] +} + +--- + +declare { + key = "span using all", + use = { + { key = "span priority <-", value = 5}, + { key = "span priority ->", value = 5}, + { key = "span priority <->", value = 5}, + { key = "span priority --", value = 5}, + { key = "span priority -!-", value = 5}, + { key = "span priority reversed <-", value = 5}, + { key = "span priority reversed ->", value = 5}, + { key = "span priority reversed <->", value = 5}, + { key = "span priority reversed --", value = 5}, + { key = "span priority reversed -!-", value = 5}, + }, + + summary = [[" + Assings a uniform priority of 5 to all edges. + "]] +} + + +-- The implementation + +-- +-- Compute a spanning tree of a graph +-- +-- The algorithm will favor nodes according to their priority. This is +-- determined through an edge priority function. +-- +-- @param ugraph An undirected graph for which the spanning tree +-- should be computed +-- @param dfs True if depth first should be used, false if breadth +-- first should be used. +-- +-- @return A new graph that is a spanning tree. + +function SpanningTreeComputation.computeSpanningTree (ugraph, dfs, events) + + local tree = Digraph.new (ugraph) -- copy vertices + + local edge_priorities = ugraph.options['/graph drawing/edge priorities'] + + local root = lib.find(ugraph.vertices, function (v) return v.options['root'] end) or ugraph.vertices[1] + + -- Traverse tree, giving preference to directed edges and, that + -- failing, to undirected and bidirected edges, and, that failing, + -- all other edges. + local marked = {} + + local stacks = { -- 10 stacks for 10 priorities, with 1 being the highest + { { parent = nil, node = root}, top = 1, bottom = 1 }, + { top = 0, bottom = 1}, + { top = 0, bottom = 1}, + { top = 0, bottom = 1}, + { top = 0, bottom = 1}, + { top = 0, bottom = 1}, + { top = 0, bottom = 1}, + { top = 0, bottom = 1}, + { top = 0, bottom = 1}, + { top = 0, bottom = 1} + } + + local function stack_is_non_empty (s) return s.top >= s.bottom end + + while lib.find(stacks, stack_is_non_empty) do + local parent, node + + for _,stack in ipairs(stacks) do + if stack_is_non_empty(stack) then + -- Pop + parent = stack[stack.top].parent + node = stack[stack.top].node + + stack[stack.top] = nil + stack.top = stack.top - 1 + + break + end + end + + if not marked[node] then + + -- The node is good! + marked[node] = true + + if parent then + tree:connect(parent,node) + end + + local arcs = ugraph:outgoing(node) + + for j=1,#arcs do + local arc = arcs[dfs and j or #arcs - j + 1] + local head = arc.head + + if not marked[head] then + local priority = arc:spanPriority() + local stack = assert(stacks[priority], "illegal edge priority") + if dfs then + stack.top = stack.top + 1 + stack[stack.top] = { parent = node, node = head} + else + stack.bottom = stack.bottom - 1 + stack[stack.bottom] = { parent = node, node = head} + end + end + end + end + end + + -- Now, copy vertex list + local copy = {} + for i,v in ipairs(tree.vertices) do + copy[i] = v + end + + -- Now, setup child lists + for _,v in ipairs(copy) do + + -- Children as they come from the spanning tree computation + tree:sortOutgoing(v, function (a,b) return a:eventIndex() < b:eventIndex() end) + local outgoings = tree:outgoing(v) + + -- Compute children as they come in the event list: + local children = {} + + local i = (v.event.index or 0)+1 + while i <= #events and events[i].kind == "edge" do + i = i + 1 + end + + if events[i] and events[i].kind == "begin" and events[i].parameters == "descendants" then + -- Ok, the node is followed by a descendants group + -- Now scan for nodes that are not inside a descendants group + local stop = events[i].end_index + local j = i+1 + while j <= stop do + if events[j].kind == "node" then + children[#children+1] = events[j].parameters + elseif events[j].kind == "begin" and events[j].parameters == "descendants" then + j = events[j].end_index + end + j = j + 1 + end + + -- Test, whether outgoings and children contain the same nodes: + local function same_elements() + local hash = {} + for v,c in ipairs(outgoings) do + hash[c.head] = true + end + local count = 0 + for _,c in pairs(children) do + if c ~= "" then + count = count + 1 + if not hash[c] or count > #outgoings then + return false + end + end + end + return count == #outgoings + end + + if same_elements() and #outgoings > 0 then + -- increase number of children, if necessary + local needed = math.max(#children, lib.lookup_option('minimum number of children', v, ugraph)) + for i=1,#children do + if children[i] ~= "" then + local d = children[i].options['desired child index'] + needed = d and math.max(needed, d) or needed + end + end + + local new_children = {} + for i=1,#children do + if children[i] ~= "" then + local d = children[i].options['desired child index'] + if d then + local target = d + + while new_children[target] do + target = 1 + (target % #children) + end + new_children[target] = children[i] + end + end + end + for i=1,#children do + if children[i] ~= "" then + local d = children[i].options['desired child index'] + if not d then + local target = i + + while new_children[target] do + target = 1 + (target % #children) + end + new_children[target] = children[i] + end + end + end + for i=1,needed do + if not new_children[i] then + local new_child = Vertex.new{ kind = "dummy" } + new_children[i] = new_child + tree:add {new_child} + tree:connect(v,new_child) + end + end + + tree:orderOutgoing(v,new_children) + end + end + end + + tree.root = root + + return tree +end + + + +-- Done + +return SpanningTreeComputation diff --git a/graphics/pgf/base/tex/generic/graphdrawing/lua/pgf/gd/trees/doc.lua b/graphics/pgf/base/tex/generic/graphdrawing/lua/pgf/gd/trees/doc.lua new file mode 100644 index 0000000000..a44fd47031 --- /dev/null +++ b/graphics/pgf/base/tex/generic/graphdrawing/lua/pgf/gd/trees/doc.lua @@ -0,0 +1,340 @@ +-- Copyright 2012 by Till Tantau +-- +-- This file may be distributed an/or modified +-- +-- 1. under the LaTeX Project Public License and/or +-- 2. under the GNU Public License +-- +-- See the file doc/generic/pgf/licenses/LICENSE for more information + +-- @release $Header$ + + +local key = require 'pgf.gd.doc'.key +local documentation = require 'pgf.gd.doc'.documentation +local summary = require 'pgf.gd.doc'.summary +local example = require 'pgf.gd.doc'.example + + +-------------------------------------------------------------------- +key "tree layout" + +summary "This layout uses the Reingold--Tilform method for drawing trees." + +documentation +[[ +The Reingold--Tilford method is a standard method for drawing +trees. It is described in: +% +\begin{itemize} + \item + E.~M.\ Reingold and J.~S.\ Tilford, + \newblock Tidier drawings of trees, + \newblock \emph{IEEE Transactions on Software Engineering,} + 7(2), 223--228, 1981. +\end{itemize} +% +My implementation in |graphdrawing.trees| follows the following paper, which +introduces some nice extensions of the basic algorithm: +% +\begin{itemize} + \item + A.\ Br\"uggemann-Klein, D.\ Wood, + \newblock Drawing trees nicely with \TeX, + \emph{Electronic Publishing,} 2(2), 101--115, 1989. +\end{itemize} +% +As a historical remark, Br\"uggemann-Klein and Wood have implemented +their version of the Reingold--Tilford algorithm directly in \TeX\ +(resulting in the Tree\TeX\ style). With the power of Lua\TeX\ at +our disposal, the 2012 implementation in the |graphdrawing.tree| +library is somewhat more powerful and cleaner, but it really was an +impressive achievement to implement this algorithm back in 1989 +directly in \TeX. + +The basic idea of the Reingold--Tilford algorithm is to use the +following rules to position the nodes of a tree (the following +description assumes that the tree grows downwards, other growth +directions are handled by the automatic orientation mechanisms of +the graph drawing library): +% +\begin{enumerate} + \item For a node, recursively compute a layout for each of its children. + \item Place the tree rooted at the first child somewhere on the page. + \item Place the tree rooted at the second child to the right of the + first one as near as possible so that no two nodes touch (and such + that the |sibling sep| padding is not violated). + \item Repeat for all subsequent children. + \item Then place the root above the child trees at the middle + position, that is, at the half-way point between the left-most and + the right-most child of the node. +\end{enumerate} +% +The standard keys |level distance|, |level sep|, |sibling distance|, +and |sibling sep|, as well as the |pre| and |post| versions of these +keys, as taken into consideration when nodes are positioned. See also +Section~\ref{subsection-gd-dist-pad} for details on these keys. + +\noindent\textbf{Handling of Missing Children.} +As described in Section~\ref{section-gd-missing-children}, you can +specify that some child nodes are ``missing'' in the tree, but some +space should be reserved for them. This is exactly what happens: +When the subtrees of the children of a node are arranged, each +position with a missing child is treated as if a zero-width, +zero-height subtree were present at that positions: +% +\begin{codeexample}[preamble={\usetikzlibrary{graphs,graphdrawing} + \usegdlibrary{trees}}] +\tikz [tree layout, nodes={draw,circle}] + \node {r} + child { node {a} + child [missing] + child { node {b} } + } + child[missing]; +\end{codeexample} +% +or in |graph| syntax: +% +\begin{codeexample}[preamble={\usetikzlibrary{graphs,graphdrawing} + \usegdlibrary{trees}}] + \tikz \graph [tree layout, nodes={draw,circle}] + { + r -> { + a -> { + , %missing + b}, + % missing + } + }; +\end{codeexample} +% +More than one child can go missing: +% +\begin{codeexample}[preamble={\usetikzlibrary{graphs,graphdrawing} + \usegdlibrary{trees}}] +\tikz \graph [tree layout, nodes={draw,circle}, sibling sep=0pt] + { r -> { a, , ,b -> {c,d}, ,e} }; +\end{codeexample} +% +Although missing children are taken into consideration for the +computation of the placement of the children of a root node relative +to one another and also for the computation of the position of the +root node, they are usually \emph{not} considered as part of the +``outline'' of a subtree (the \texttt{minimum number of children} +key ensures that |b|, |c|, |e|, and |f| all have a missing right +child): +% +\begin{codeexample}[preamble={\usetikzlibrary{graphs,graphdrawing} + \usegdlibrary{trees}}] +\tikz \graph [tree layout, minimum number of children=2, + nodes={draw,circle}] + { a -> { b -> c -> d, e -> f -> g } }; +\end{codeexample} +% +This behaviour of ``ignoring'' missing children in later stages of +the recursion can be changed using the key |missing nodes get space|. + +\noindent\textbf{Significant Pairs of Siblings.} +Br\"uggemann-Klein and Wood have proposed an extension of the +Reingold--Tilford method that is intended to better highlight the +overall structure of a tree. Consider the following two trees: +% +\begin{codeexample}[preamble={\usetikzlibrary{graphs,graphdrawing} + \usegdlibrary{trees}}] +\tikz [baseline=(a.base), tree layout, minimum number of children=2, + sibling distance=5mm, level distance=5mm] + \graph [nodes={circle, inner sep=0pt, minimum size=2mm, fill, as=}]{ + a -- { b -- c -- { d -- e, f -- { g, h }}, i -- j -- k[second] } + };\quad +\tikz [baseline=(a.base), tree layout, minimum number of children=2, + sibling distance=5mm, level distance=5mm] + \graph [nodes={circle, inner sep=0pt, minimum size=2mm, fill, as=}]{ + a -- { b -- c -- d -- e, i -- j -- { f -- {g,h}, k } } + }; +\end{codeexample} +% +As observed by Br\"uggemann-Klein and Wood, the two trees are +structurally quite different, but the Reingold--Tilford method +places the nodes at exactly the same positions and only one edge +``switches'' positions. In order to better highlight the differences +between the trees, they propose to add a little extra separation +between siblings that form a \emph{significant pair}. They define +such a pair as follows: Consider the subtrees of two adjacent +siblings. There will be one or more levels where these subtrees have +a minimum distance. For instance, the following two trees the +subtrees of the nodes |a| and |b| have a minimum distance only at +the top level in the left example, and in all levels in the second +example. A \emph{significant pair} is a pair of siblings where the +minimum distance is encountered on any level other than the first +level. Thus, in the first example there is no significant pair, +while in the second example |a| and |b| form such a pair. +% +\begin{codeexample}[preamble={\usetikzlibrary{graphs,graphdrawing} + \usegdlibrary{trees}}] +\tikz \graph [tree layout, minimum number of children=2, + level distance=5mm, nodes={circle,draw}] + { / -> { a -> / -> /, b -> /[second] -> /[second] }}; + \quad +\tikz \graph [tree layout, minimum number of children=2, + level distance=5mm, nodes={circle,draw}] + { / -> { a -> / -> /, b -> / -> / }}; +\end{codeexample} +% +Whenever the algorithm encounters a significant pair, it adds extra +space between the siblings as specified by the |significant sep| +key. +]] + + +example +[[ +\tikz [tree layout, sibling distance=8mm] + \graph [nodes={circle, draw, inner sep=1.5pt}]{ + 1 -- { 2 -- 3 -- { 4 -- 5, 6 -- { 7, 8, 9 }}, 10 -- 11 -- { 12, 13 } } + }; +]] + + +example +[[ +\tikz [tree layout, grow=-30, + sibling distance=0mm, level distance=0mm,] + \graph [nodes={circle, draw, inner sep=1.5pt}]{ + 1 -- { 2 -- 3 -- { 4 -- 5, 6 -- { 7, 8, 9 }}, 10 -- 11 -- { 12, 13 } } + }; +]] +-------------------------------------------------------------------- + + + +-------------------------------------------------------------------- +key "missing nodes get space" + +summary +[[ +When set to true, missing children are treated as if they where +zero-width, zero-height nodes during the whole tree layout process. +]] + + +example +[[ +\tikz \graph [tree layout, missing nodes get space, + minimum number of children=2, nodes={draw,circle}] +{ a -> { b -> c -> d, e -> f -> g } }; +]] +-------------------------------------------------------------------- + + + + + +-------------------------------------------------------------------- +key "significant sep" + +summary +[[ +This space is added to significant pairs by the modified +Reingold--Tilford algorithm. +]] + +example +[[ +\tikz [baseline=(a.base), tree layout, significant sep=1em, + minimum number of children=2, + sibling distance=5mm, level distance=5mm] + \graph [nodes={circle, inner sep=0pt, minimum size=2mm, fill, as=}]{ + a -- { b -- c -- { d -- e, f -- { g, h }}, i -- j -- k[second] } + };\quad +\tikz [baseline=(a.base), tree layout, significant sep=1em, + minimum number of children=2, + sibling distance=5mm, level distance=5mm] + \graph [nodes={circle, inner sep=0pt, minimum size=2mm, fill, as=}]{ + a -- { b -- c -- d -- e, i -- j -- { f -- {g,h}, k } } + }; +]] +-------------------------------------------------------------------- + + + +-------------------------------------------------------------------- +key "binary tree layout" + +summary +[[ +A layout based on the Reingold--Tilford method for drawing +binary trees. +]] + +documentation +[[ +This key executes: +% +\begin{enumerate} + \item |tree layout|, thereby selecting the Reingold--Tilford method, + \item |minimum number of children=2|, thereby ensuring the all nodes + have (at least) two children or none at all, and + \item |significant sep=10pt| to highlight significant pairs. +\end{enumerate} +% +In the examples, the last one is taken from the paper of +Br\"uggemann-Klein and Wood. It demonstrates nicely the +advantages of having the full power of \tikzname's anchoring and the +graph drawing engine's orientation mechanisms at one's disposal. +]] + + +example +[[ +\tikz [grow'=up, binary tree layout, sibling distance=7mm, level distance=7mm] + \graph { + a -- { b -- c -- { d -- e, f -- { g, h }}, i -- j -- k[second] } + }; +]] + +--[[ +% TODOsp: codeexamples: the next example needs the library `arrows.meta` +--]] +example +[[ +\tikz \graph [binary tree layout] { + Knuth -> { + Beeton -> Kellermann [second] -> Carnes, + Tobin -> Plass -> { Lamport, Spivak } + } +};\qquad +\tikz [>={Stealth[round,sep]}] + \graph [binary tree layout, grow'=right, level sep=1.5em, + nodes={right, fill=blue!50, text=white, chamfered rectangle}, + edges={decorate,decoration={snake, post length=5pt}}] + { + Knuth -> { + Beeton -> Kellermann [second] -> Carnes, + Tobin -> Plass -> { Lamport, Spivak } + } + }; +]] +-------------------------------------------------------------------- + + + +-------------------------------------------------------------------- +key "extended binary tree layout" + +summary +[[ +This algorithm is similar to |binary tree layout|, only the +option \texttt{missing nodes get space} is executed and the +\texttt{significant sep} is zero. +]] + +example +[[ +\tikz [grow'=up, extended binary tree layout, + sibling distance=7mm, level distance=7mm] + \graph { + a -- { b -- c -- { d -- e, f -- { g, h }}, i -- j -- k[second] } + }; +]] +-------------------------------------------------------------------- diff --git a/graphics/pgf/base/tex/generic/graphdrawing/lua/pgf/gd/trees/library.lua b/graphics/pgf/base/tex/generic/graphdrawing/lua/pgf/gd/trees/library.lua new file mode 100644 index 0000000000..a0019666ac --- /dev/null +++ b/graphics/pgf/base/tex/generic/graphdrawing/lua/pgf/gd/trees/library.lua @@ -0,0 +1,35 @@ +-- Copyright 2012 by Till Tantau +-- +-- This file may be distributed an/or modified +-- +-- 1. under the LaTeX Project Public License and/or +-- 2. under the GNU Public License +-- +-- See the file doc/generic/pgf/licenses/LICENSE for more information + +-- @release $Header$ + + + +--- +-- \tikzname\ offers several different syntax to specify trees (see +-- Sections \ref{section-library-graphs} +-- and~\ref{section-trees}). The job of the graph drawing algorithms from +-- this library is to turn the specification of trees into beautiful +-- layouts. +-- +-- We start this section with a description of algorithms, then we have a +-- look at how missing children can be specified and at what happens when +-- the input graph is not a tree. +-- +-- @library + +local trees -- Library name + +-- Load declarations from: +require "pgf.gd.trees.ChildSpec" +require "pgf.gd.trees.SpanningTreeComputation" + +-- Load algorithms from: +require "pgf.gd.trees.ReingoldTilford1981" + diff --git a/graphics/pgf/base/tex/generic/graphdrawing/tex/experimental/tikzlibrarygraphdrawing.evolving.code.tex b/graphics/pgf/base/tex/generic/graphdrawing/tex/experimental/tikzlibrarygraphdrawing.evolving.code.tex new file mode 100644 index 0000000000..efa214a12e --- /dev/null +++ b/graphics/pgf/base/tex/generic/graphdrawing/tex/experimental/tikzlibrarygraphdrawing.evolving.code.tex @@ -0,0 +1,21 @@ +% Copyright 2018 by Malte Skambath +% +% This file may be distributed and/or modified +% +% 1. under the LaTeX Project Public License and/or +% 2. under the GNU Public License. +% +% See the file doc/generic/pgf/licenses/LICENSE for more details. + +\usepgflibrary{graphdrawing} + +\usegdlibrary{evolving} + +\newcount\mycount +\tikzgraphsset{ + nodes = {supernode/.expanded=\tikzgraphnodename}, + when/.code = {\pgfparsetime{#1}\mycount=\pgftimeresult pt\tikzgraphsset{name/.expanded=\the\mycount,snapshot/.expanded=\the\mycount}} +} + + +\endinput diff --git a/graphics/pgf/base/tex/generic/graphdrawing/tex/pgflibrarygraphdrawing.circular.code.tex b/graphics/pgf/base/tex/generic/graphdrawing/tex/pgflibrarygraphdrawing.circular.code.tex new file mode 100644 index 0000000000..2fec25131d --- /dev/null +++ b/graphics/pgf/base/tex/generic/graphdrawing/tex/pgflibrarygraphdrawing.circular.code.tex @@ -0,0 +1,16 @@ +% Copyright 2019 by Renée Ahrens, Olof Frahm, Jens Kluttig, Matthias Schulz, Stephan Schuster +% Copyright 2019 by Till Tantau +% +% This file may be distributed and/or modified +% +% 1. under the LaTeX Project Public License and/or +% 2. under the GNU Public License. +% +% See the file doc/generic/pgf/licenses/LICENSE for more details. + +\ProvidesFileRCS{pgflibrarygraphdrawing.circular.code.tex} + + +\usegdlibrary{circular}% + +\endinput diff --git a/graphics/pgf/base/tex/generic/graphdrawing/tex/pgflibrarygraphdrawing.code.tex b/graphics/pgf/base/tex/generic/graphdrawing/tex/pgflibrarygraphdrawing.code.tex new file mode 100644 index 0000000000..63efd352f1 --- /dev/null +++ b/graphics/pgf/base/tex/generic/graphdrawing/tex/pgflibrarygraphdrawing.code.tex @@ -0,0 +1,1165 @@ +% Copyright 2018 by Renée Ahrens, Olof Frahm, Jens Kluttig, Matthias Schulz, Stephan Schuster +% Copyright 2018 by Till Tantau +% +% This file may be distributed and/or modified +% +% 1. under the LaTeX Project Public License and/or +% 2. under the GNU Public License. +% +% See the file doc/generic/pgf/licenses/LICENSE for more details. + +\ProvidesFileRCS{pgflibrarygraphdrawing.code.tex} + + + +% check if luatex is running + +\ifx\directlua\relax% + \pgferror{You need to run LuaTeX to use the graph drawing library} + \expandafter\endinput +\fi +\ifx\directlua\undefined% + \pgferror{You need to run LuaTeX to use the graph drawing library} + \expandafter\endinput +\fi + + + + +% +% All graph drawing keys normally live in the following namespace: +% /graph drawing. +% + +\def\pgfgdset{\pgfqkeys{/graph drawing}}% + + +% Setup a key +% +% #1 = key +% +% Description: +% +% An internal macro that sets up #1 as a graph drawing key. + +\def\pgfgd@callbackkey#1{% + \pgf@gd@setup@forwards{#1}% + \pgfutil@g@addto@macro\pgf@gd@forwards@list{\pgf@gd@setup@forwards{#1}}% +}% +\def\pgf@gd@setup@forwards#1{% + \let\pgf@temp\pgfutil@empty + \foreach \pgf@gd@path in \pgf@gd@forwarding@list {% + \ifx\pgf@gd@path\pgfutil@empty\else% + \expandafter\pgfutil@g@addto@macro% + \expandafter\pgf@temp\expandafter{% + \expandafter\pgfkeys\expandafter{\pgf@gd@path#1/.forward to=/graph drawing/#1}}% + \fi% + }% + \pgf@temp +}% + +\let\pgf@gd@forwards@list\pgfutil@empty + + +% Append to the forwarding list: +% +% #1 = paths to append to the forwarding list +% +% Description: +% +% Append the paths in #1 (with trailing slashes) to the forwarding +% list. +% +% If algorithms have already been declared, forwarding will also be +% setup for them (using a bit of magic...). + +\def\pgfgdappendtoforwardinglist#1{% + \let\pgf@gd@forwarding@list@orig\pgf@gd@forwarding@list + \def\pgf@gd@forwarding@list{#1}% + \pgf@gd@forwards@list% + \expandafter\def\expandafter\pgf@gd@forwarding@list\expandafter{\pgf@gd@forwarding@list@orig,#1}% +}% +\let\pgf@gd@forwarding@list\pgfutil@empty + + + +% +% +% Callbacks +% +% The following macros are called *from* the binding layer. You do not +% call them from the pgf layer. +% +% + + +% Callback for declaring a graph parameter key +% +% #1 = Parameter key +% #2 = Parameter type +% +% Description: +% +% When a graph drawing algorithm starts working, a set of options, +% called "graph drawing parameters" in the following, can influence the +% way the algorithm works. For instance, an graph drawing parameter +% might be the average distance between vertices which the algorithm +% should take into account. Another example might be the fact the +% certain nodes are special nodes and that a certain edge should have +% a large label. +% +% These graph drawing parameters are different from "usual" pgf +% options: An algorithmic parameter influenced the way the algorithm +% works, while usual options normally only influence how the result +% looks like. For instance, the fact that a node is red is not an +% graph drawing parameter (usually, at least), while the shape of a node +% might be an graph drawing parameter. +% +% Graph drawing parameters are declared by the algorithmic layer +% (since only this layer "knows" which parameters there are). The +% binding to TeX will call the \pgfgddeclareparameter function +% internally. +% +% Specifying the set of graph drawing parameters for a given graph or +% node or edge works as follows: When the graph drawing engine is +% started for a graph (using \pgfgdbeginscope), a snapshot is taken of +% all graph drawing parameters currently setup at this +% point. Similarly, when a node is created inside such a scope, a +% snapshot is taken of the set of all graph drawing parameters in +% force at this point is taken and stored together with the +% node. Finally, when an edge is created, a snapshot of the setting of +% the graph drawing parameters is taken. + +\def\pgfgdcallbackdeclareparameter#1#2{% + \pgfkeysdef{/graph drawing/#1}{\pgf@gd@handle@parameter{#1}{#2}{##1}}% + \pgfgd@callbackkey{#1}% +}% +\def\pgf@gd@handle@parameter#1#2#3{% + \def\pgf@temp{#3}% + \ifx\pgf@temp\pgfkeysnovalue@text% + \let\pgfgdresult\pgfgd@niltext% + \else% + \pgfkeys{/graph drawing/conversions/#2={#3}}% + \fi% + \advance\pgf@gd@parameter@stack@height by1\relax% + \pgf@gd@parameter@stack@height\directlua{% + local new, main = pgf.gd.interface.InterfaceToDisplay.pushOption('\pgfutil@luaescapestring{#1}',\pgfgdresult,\the\pgf@gd@parameter@stack@height) + tex.print(new..'\pgfutil@luaescapestring{\relax}') + if main then + tex.print('\pgfutil@luaescapestring{\noexpand\pgfgdtriggerrequest}') + end}% +}% +\newcount\pgf@gd@parameter@stack@height + +\def\pgfgdtriggerrequest{\pgfgdset{@request scope and layout}}% + + +% Conversions are used to adapt the syntactic description of a +% parameter on the TikZ layer to the one expected by Lua. The set of +% permissible conversions is described in +% InterfaceToAlgorithms.declareParameter. + +\pgfgdset{ + conversions/string/.code=\def\pgfgdresult{'\pgfutil@luaescapestring{\detokenize{#1}}'}, + conversions/raw/.code=\def\pgfgdresult{#1}, + conversions/nil/.code=\let\pgfgdresult\pgfgd@niltext, + conversions/boolean/.code=\def\pgf@test{#1}\ifx\pgf@test\pgf@truetext\def\pgfgdresult{true}\else\ifx\pgf@test\pgfkeysnovalue@text\def\pgfgdresult{true}\else\def\pgfgdresult{false}\fi\fi, + conversions/number/.code=\pgfmathparse{#1}\let\pgfgdresult\pgfmathresult, + conversions/length/.code=\pgfmathparse{#1}\let\pgfgdresult\pgfmathresult, + conversions/time/.code=\pgfparsetime{#1}\let\pgfgdresult\pgftimeresult, + conversions/direction/.code=\pgf@lib@gd@grow@dir{#1}\let\pgfgdresult\pgfmathresult, + conversions/canvas coordinate/.code args={(#1pt,#2pt)}{\edef\pgfgdresult{pgf.gd.model.Coordinate.new(#1,#2)}}, + % deprecated, will be removed: + conversions/coordinate/.code args={(#1pt,#2pt)}{\edef\pgfgdresult{{#1,#2}}} +}% +\def\pgf@truetext{true}% +\def\pgfgd@niltext{nil}% + +\def\pgf@lib@gd@grow@dir#1{% + \def\pgf@temp{#1}% + \ifx\pgf@temp\pgf@gd@lib@active@bar% + \def\pgfmathresult{-90} + \else% + \ifcsname pgf@orient@direction@#1\endcsname% + \expandafter\let\expandafter\pgfmathresult\csname pgf@orient@direction@#1\endcsname% + \else + \pgfmathparse{#1}% + \fi + \fi +}% + +\def\pgf@orient@direction@down{-90}% +\def\pgf@orient@direction@up{90}% +\def\pgf@orient@direction@left{180}% +\def\pgf@orient@direction@right{0}% + +\def\pgf@orient@direction@south{-90}% +\def\pgf@orient@direction@north{90}% +\def\pgf@orient@direction@west{180}% +\def\pgf@orient@direction@east{0}% + +\expandafter\def\csname pgf@orient@direction@north east\endcsname{45}% +\expandafter\def\csname pgf@orient@direction@north west\endcsname{135}% +\expandafter\def\csname pgf@orient@direction@south east\endcsname{-45}% +\expandafter\def\csname pgf@orient@direction@south west\endcsname{-135}% + +\expandafter\def\csname pgf@orient@direction@-\endcsname{0}% +\expandafter\def\csname pgf@orient@direction@|\endcsname{-90}% + +{% + \catcode`\|=13 + \gdef\pgf@gd@lib@active@bar{|}% +}% + + + + + +% Callback for starting the rendering of a collection kind +% +% #1 = Kind +% #2 = Layer +% +% Description: +% +% Executes /graph drawing/#1/begin rendering/.try +% + +\def\pgfgdcallbackrendercollectionkindstart#1#2{% + \ifnum#2<0\relax + \setbox\pgf@gd@prekind@box=\hbox\bgroup% + \unhbox\pgf@gd@prekind@box% + \else% + \setbox\pgf@gd@postkind@box=\hbox\bgroup% + \unhbox\pgf@gd@postkind@box% + \fi% + \pgfgdset{/graph drawing/#1/begin rendering/.try}% +}% + +% Callback for ending the rendering of a collection kind +% +% #1 = Kind +% #2 = Layer +% +% Description: +% +% Executes /graph drawing/#1/end rendering/.try +% + +\def\pgfgdcallbackrendercollectionkindstop#1#2{% + \pgfgdset{/graph drawing/#1/end rendering/.try}% + \egroup % close box +}% + + + +% Callback for rendering a collection +% +% #1 = Kind +% #2 = Options +% +% Description: +% +% Executes /graph drawing/#1/render/.try={#2} +% + +\def\pgfgdcallbackrendercollection#1#2{ + \pgfgdset{/graph drawing/#1/render/.try={#2}} +}% + + + + + +% Graph events +% +% Although a graph consists of nodes and edges, during the +% construction of the graph a lot of information concerning the +% structure of the graph is often available. For instance, as we +% specify a graph using the child-syntax, we do not only which edges +% are present, but we can implicitly specify an ordering on the +% nodes. Even more, there is even information available concerning +% nodes that are not present at all: A child[missing] is not present +% as a node or an edge, but a tree drawing algorithm will want to know +% about this child nevertheless. +% +% In order to communicate such information to the graph drawing +% engine, "events" are used. As a graph is created, in addition to +% nodes and edges, "events" may happen. The events come in a +% sequential order and they are stored together with the graph. For +% each node and each edge, its index in the event sequence is +% stored, that is, it is stored how many events happened before the +% node or edge was created. +% +% Internally, an event consists of a name and, possibly, some +% parameters. When the parameter is created on the tikz level, it will +% be a string that is passed down to Lua. Internally created events +% will also have parameters that are objects. +% +% Two events are a bit special since they get special internal +% support: The begin and end events. The first signals that some kind +% of group has started; which is closed by the corresponding end +% event. The "kind" of group is indicated by the parameter of the +% begin event. +% +% +% +% Standard events are: +% +% For each node entered into a graph, a "node" event is automatically +% created internally with the parameter being the node. However, you +% can also create this event yourself. In this case, the parameter +% will be a string and will mean that the node is "virtual" or +% "missing", but space should be reserved for it, if possible (this is +% use, for instance, by certain tree layouts). +% +% For each edge entered into a graph, an "edge" event is automatically +% created, with the edge as the parameter. Again, an event with a +% string parameter corresponds to a "non-existing" node. +% +% +% Standard event groups are: +% +% +% The "descendants" event group include a set of nodes that, at least +% inside the specification, are descendants of the last node +% +% begin descendants +% ... +% end +% +% +% The "array" event group collects together some nodes in an array of +% nodes. This can be used, for instance, to specify matrices. +% +% begin array +% ... +% end + + + +% Create a new event +% +% #1 = event name (should be a valid lua identifier name) +% #2 = parameter (some text) +% +% Description: +% +% Adds a new event to the event sequence of the graph + +\def\pgfgdevent#1#2{% + \directlua{pgf.gd.interface.InterfaceToDisplay.createEvent('\pgfutil@luaescapestring{#1}', '\pgfutil@luaescapestring{#2}')}% +}% + + +% Start an event group +% +% #1 = kind of event group +% +% Description: +% +% Creates a begin event with #1 as the parameter of the begin +% event. + +\def\pgfgdbegineventgroup#1{% + \pgfgdevent{begin}{#1}% +}% + +% End an event group +% +% Description: +% +% Creates an end event. + +\def\pgfgdendeventgroup{% + \pgfgdevent{end}{}% +}% + + +% Creates an event group for the current TeX group +% +% #1 = event group name +% +% Description: +% +% Issues a begin event for #1 and, using \aftergroup, adds an end +% event at the end of the current tex group. + +\def\pgfgdeventgroup#1{% + \pgfgdbegineventgroup{#1}% + \aftergroup\pgfgdendeventgroup% +}% + + + + + +% +% Nodes +% + + + +% +% Callback method for \pgfpositionnodelater +% +% This function is called by \pgfnode whenever a node has been newly +% created inside a graph drawing scope. It will create a new vertex on +% the Lua layer. +% +\def\pgf@gd@positionnode@callback{% + {% + % evaluate coordinates + \pgfmathsetmacro{\pgf@gd@node@minx}{\pgfpositionnodelaterminx}% + \pgfmathsetmacro{\pgf@gd@node@miny}{\pgfpositionnodelaterminy}% + \pgfmathsetmacro{\pgf@gd@node@maxx}{\pgfpositionnodelatermaxx}% + \pgfmathsetmacro{\pgf@gd@node@maxy}{\pgfpositionnodelatermaxy}% + % Assemble the Path object: + \directlua{pgf.temp = require 'pgf.gd.model.Path'.new ()}% + \let\pgfsyssoftpath@movetotoken\pgf@gd@movetotoken% + \let\pgfsyssoftpath@linetotoken\pgf@gd@linetotoken% + \let\pgfsyssoftpath@rectcornertoken\pgf@gd@rectcornertoken% + \let\pgfsyssoftpath@curvetosupportatoken\pgf@gd@curvetosupportatoken% + \let\pgfsyssoftpath@closepathtoken\pgf@gd@closepathtoken% + \pgfpositionnodelaterpath% + % + \pgfmathsetlength\pgf@xa{\pgfkeysvalueof{/pgf/outer xsep}}% + \pgfmathsetlength\pgf@ya{\pgfkeysvalueof{/pgf/outer ysep}}% + % + {% + \pgf@process{\pgfpointanchor{\pgfpositionnodelatername}{center}} + \pgfsettransform{\csname pgf@sh@nt@\pgfpositionnodelatername\endcsname}% + \pgf@pos@transform{\pgf@x}{\pgf@y}% + \global\pgf@x=\pgf@x% + \global\pgf@y=\pgf@y% + }% + % call lua system library to create a lua node object + \directlua{ + pgf.gd.interface.InterfaceToDisplay.createVertex( + string.sub('\pgfutil@luaescapestring{\pgfpositionnodelatername}',30), % strip "not yet positionedPGFINTERNAL" + '\pgfutil@luaescapestring{\csname pgf@sh@ns@\pgfpositionnodelatername\endcsname}', + pgf.temp:pad((\pgf@sys@tonumber\pgf@xa+\pgf@sys@tonumber\pgf@ya)/2), % Padded path + \the\pgf@gd@parameter@stack@height, % Stack height + {% Binding info + x_min = \pgf@gd@node@minx, + y_min = \pgf@gd@node@miny, + x_max = \pgf@gd@node@maxx, + y_max = \pgf@gd@node@maxy, + tex_box_number = \pgfpositionnodelaterbox + }, + {% Anchors + center = require 'pgf.gd.model.Coordinate'.new(\pgf@sys@tonumber\pgf@x,\pgf@sys@tonumber\pgf@y) + } + ) + pgf.temp = nil + } + }% +}% + + +\def\pgf@gd@movetotoken#1#2{\directlua{pgf.temp:appendMoveto(\Pgf@geT#1,\Pgf@geT#2)}}% +\def\pgf@gd@linetotoken#1#2{\directlua{pgf.temp:appendLineto(\Pgf@geT#1,\Pgf@geT#2)}}% +\def\pgf@gd@rectcornertoken#1#2#3#4#5{% + \directlua{% + local x,y,dx,dy=\Pgf@geT#1,\Pgf@geT#2,\Pgf@geT#4,\Pgf@geT#5 + pgf.temp:appendMoveto(x,y) + pgf.temp:appendLineto(x,y+dy) + pgf.temp:appendLineto(x+dx,y+dy) + pgf.temp:appendLineto(x+dx,y) + pgf.temp:appendClosepath()% + }% +}% +\def\pgf@gd@curvetosupportatoken#1#2#3#4#5#6#7#8{\directlua{pgf.temp:appendCurveto(\Pgf@geT#1,\Pgf@geT#2,\Pgf@geT#4,\Pgf@geT#5,\Pgf@geT#7,\Pgf@geT#8)}}% +\def\pgf@gd@closepathtoken#1#2{\directlua{pgf.temp:appendClosepath()}}% + + +% Set options for an already existing node +% +% #1 = node name +% +% These node parameters of #1 will be updated with the current values +% of the node parameters. The node #1 must previously have been passed +% to the gd engine. If some of the options have already been set for +% the node, these settings will be overruled. + +\def\pgfgdsetlatenodeoption#1{% + \directlua{ + pgf.gd.interface.InterfaceToDisplay.addToVertexOptions('\pgfutil@luaescapestring{#1}',\the\pgf@gd@parameter@stack@height) + } +}% + + + +% +% A callback for rendering (finally positioning) a node +% +% #1 = name of the node +% #2 = x min of the bounding box +% #3 = x max of the bounding box +% #4 = y min of the bounding box +% #5 = y max of the bounding box +% #6 = desired x pos of the node +% #7 = desired y pos of the node +% #8 = box register number of the TeX node +% #9 = animation code +% +% This callback will be called by the engine for every original node +% when it finally needs to placed at a final position. + +\def\pgfgdcallbackrendernode#1#2#3#4#5#6#7#8#9{% + {% + \def\pgfpositionnodelatername{#1} + \def\pgfpositionnodelaterminx{#2} + \def\pgfpositionnodelatermaxx{#3} + \def\pgfpositionnodelaterminy{#4} + \def\pgfpositionnodelatermaxy{#5} + \directlua{pgf.gd.interface.InterfaceCore.binding:retrieveBox(#8,\pgfpositionnodelaterbox)} + \def\pgf@temp{#9}% + \ifx\pgf@temp\pgfutil@empty% + \pgfpositionnodenow{\pgfqpoint{#6}{#7}}% + \else% + \pgfset{every graphdrawing animation/.try}% + \pgfset{every graphdrawing node animation/.try}% + #9% + \pgfuseid{pgf@gd}% + \pgfidscope% + \pgfpositionnodenow{\pgfqpoint{#6}{#7}}% + \endpgfidscope% + \fi% + }% +}% + +\ifx\pgfanimateattribute\pgfutil@undefined + \def\pgfanimateattribute#1#2{\tikzerror{You need to say \string\usetikzlibrary{animations} for animated graphs}}% +\fi + + +% Adds an edge to the graph +% +% #1 = first node +% #2 = second node +% #3 = edge direction +% #4 = edge options (will be executed in a protected environment) +% #5 = aux stuff (curtesy for TikZ -- edge nodes) +% +% Description: +% +% Creating an edge means that you tell the graph drawing algorithm +% that #1 and #2 are connected. The "kind" of connection is indicated +% by #3, which may be one of the following: +% +% -> = a directed edge (also known as an arc) from #1 to #2 +% -- = an undirected edge between #1 and #2 +% <- = a directed edge from #2 to #1, but with the "additional hint" +% that this is a "backward" edge. A graph drawing algorithm may +% or may not take this hint into account +% <-> = a bi-directed edge between #1 and #2. +% +% +% The parameters #4 and #5 are a bit more tricky. When an edge between +% two vertices of a graph is created via \pgfgdedge, nothing is +% actually done immediately. After all, without knowing the final +% positions of the nodes #1 and #2, there is no way of +% creating the actual drawing commands for the edge. Thus, the actual +% drawing of the edge is done only when the graph drawing algorithm is +% done (namely in the macro \pgfgdcallbackedge, see later). +% +% Because of this "delayed" drawing of edges, options that influence +% the edge must be retained until the moment when the edge is actually +% drawn. Parameters #4 and #5 store such options. +% +% Let us start with #4. This parameter should be set to a list of +% key-value pairs like +% +% /tikz/.cd, color=red, very thick, this edge must be vertical +% +% Some of these options may be of interest to the graph drawing +% algorithm (like the last option) while others will +% only be important during the drawing of edge (like the first +% option). The options that are important for the graph drawing +% algorithm must be passed to the algorithm via setting keys that have +% been declared using the handler .edge parameter (see +% above). +% +% The tricky part is that options that are of interest to the graph +% drawing algorithm must be executed *before* the algorithm starts, +% but the options as a whole are usually only executed during the +% drawing of the edges, which is *after* the algorithm has finished. +% +% To overcome this problem, the following happens: +% +% The options in #4 are executed "tentatively" inside +% \pgfgdedge. However, this execution is done in a "heavily guarded +% sandbox" where all effects of the options (like changing the +% color or the line width) do not propagate beyond the sandbox. Only +% the changes of the graph drawing edge parameters leave the +% sandbox. These parameters are then passed down to the graph drawing +% engine. +% +% Later, when the edge is drawn using \pgfgdcallbackedge, the options #4 +% are available once more and then they are executed normally. +% +% Note that when the options in #4 are executed, no path is +% preset. Thus, you typically need to start it with, say, /tikz/.cd. +% +% +% The text in #5 is some "auxiliary" text that is simply stored away +% and later directly to \pgfgdcallbackedge. This is a curtesy to TikZ, +% which can use it to store its node labels. +% +% Example: +% +% \pgfgdedge{A}{B}{->}{red}{} +% +\def\pgfgdedge#1#2#3#4#5{% + % Ok, setup sandbox + \begingroup% + \setbox0=\hbox{{% + \pgfinterruptpath% + \pgfgdprepareedge% + \pgfkeys{#4}% + % create edge in Lua + \toks0={#4}% + \toks1={#5}% + \directlua{ + pgf.gd.interface.InterfaceToDisplay.createEdge( + '\pgfutil@luaescapestring{#1}','\pgfutil@luaescapestring{#2}','\pgfutil@luaescapestring{#3}', + \the\pgf@gd@parameter@stack@height, + { pgf_options = '\pgfutil@luaescapestring{\the\toks0}', + pgf_edge_nodes = '\pgfutil@luaescapestring{\the\toks1}', + }) + }% + \endpgfinterruptpath% + }}% + \endgroup% +}% + +\let\pgfgdprepareedge=\pgfutil@empty +\def\pgfgdaddprepareedgehook#1{\expandafter\def\expandafter\pgfgdprepareedge\expandafter{\pgfgdprepareedge#1}}% + + +\newif\ifpgf@gd@nodes@behind@edges + + + +% Define a callback for rendering edges +% +% #1 = macro name +% +% Descriptions: +% +% This is a callback from the graph drawing engine. At the end of the +% creation of a graph, when the nodes have been positioned, this macro +% is called once for each edge. The macro should take the following +% parameters: +% +% #1 = from node, optionally followed by "." and the tail anchor +% #2 = to node, optionally followed by "." and the head anchor +% #3 = direction (<-, --, ->, or <->) +% #4 = original options +% #5 = aux text (typically edge nodes) +% #6 = algorithm-generated options +% #7 = bend information +% #8 = animations +% +% The first five parameters are the original values that were passed +% down to the \pgfgdedge command. +% +% #6 contains options that have been "computed by the algorithm". For +% instance, an algorithm might have determined, say, flow capacities +% for edges and it might now wish to communicate this information back +% to the upper layers. These options should be executed with the path +% /graph drawing. +% +% #7 contains algorithmically-computed information concerning how the +% edge should bend. This will be a text like +% "--(10pt,20pt)--(30pt,40pt)" in tikz-syntax, using the path +% operations "--", "..controls" and "--cycle". +% +% By default, a simple line is drawn between the nodes. Usually, you +% will wish to install a more "fancy" callback, here. + +\def\pgfgdsetedgecallback#1{\let\pgfgdcallbackedge=#1}% + +\def\pgfgddefaultedgecallback#1#2#3#4#5#6#7#8{% + {% + \def\pgf@temp{#8}% + \ifx\pgf@temp\pgfutil@empty% + \else% + \pgfset{every graphdrawing animation/.try}% + \pgfset{every graphdrawing edge animation/.try}% + #8% + \pgfuseid{pgf@gd}% + \pgfidscope% + \fi% + \pgfscope + \pgfpathmoveto{ + \pgfutil@in@.{#1}% + \ifpgfutil@in@ + \pgf@gd@unravel#1\relax% + \else + \pgfpointshapeborder{#1}{\pgfpointanchor{#2}{center}} + \fi + } + \pgfpathlineto{ + \pgfutil@in@.{#2}% + \ifpgfutil@in@ + \pgf@gd@unravel#2\relax% + \else + \pgfpointshapeborder{#2}{\pgfpointanchor{#1}{center}} + \fi + } + \pgfusepath{stroke} + \endpgfscope + \ifx\pgf@temp\pgfutil@empty% + \else% + \endpgfidscope% + \fi% + } +}% + +\pgfgdsetedgecallback{\pgfgddefaultedgecallback}% + +\def\pgf@gd@unravel#1.#2\relax{% + \pgfpointanchor{#1}{#2}% +}% + + + + +% Callbacks: Called before the shipout of nodes and edges starts +% +% First, the general begin shipout is called. Then, the node shipout +% starts, the nodes are created, and then the end of the node shipout +% is signaled. Next, the edge shipout starts and ends. Finally, the +% end shipout is called. + +\def\pgfgdcallbackbeginshipout{% + \pgfscope% + \catcode`\@=11\relax% + \setbox\pgf@gd@prekind@box=\box\pgfutil@voidb@x% + \setbox\pgf@gd@postkind@box=\box\pgfutil@voidb@x% +}% +\def\pgfgdcallbackendshipout{% + \box\pgf@gd@prekind@box% + \ifpgf@gd@nodes@behind@edges% + \box\pgf@gd@node@box% + \box\pgf@gd@edge@box% + \else% + \box\pgf@gd@edge@box% + \box\pgf@gd@node@box% + \fi% + \box\pgf@gd@postkind@box% + \endpgfscope +}% + +\newbox\pgf@gd@node@box +\newbox\pgf@gd@edge@box +\newbox\pgf@gd@prekind@box +\newbox\pgf@gd@postkind@box + +\def\pgfgdcallbackbeginnodeshipout{% + \setbox\pgf@gd@node@box=\hbox\bgroup% +}% +\def\pgfgdcallbackendnodeshipout{% + \egroup% +}% + +\def\pgfgdcallbackbeginedgeshipout{% + \setbox\pgf@gd@edge@box=\hbox\bgroup% +}% + +\def\pgfgdcallbackendedgeshipout{% + \egroup +}% + + + + +% Generate a node +% +% This callback is called from the engine whenever an algorithm +% generates a new node internally. +% +% #1 = name of the node +% #2 = shape of the node +% #3 = options generated by the algorithm in key-value syntax. The set +% of generated options is algorithm-dependent. +% #4 = text +% +% This is an internal function and will be called by the binding layer + +\def\pgfgdcallbackcreatevertex#1#2#3#4{% + { + \pgfkeys{#3} + \pgfnode{#2}{\pgfkeysvalueof{/graph drawing/generated + node/anchor}}{#4}{#1}{\pgfkeysvalueof{/graph drawing/generated + node/use path}} + } +}% + +\pgfkeys{ + /graph drawing/generated node/anchor/.initial=center, + /graph drawing/generated node/use path/.initial=\pgfusepath{} +}% + + + +% +% Sublayouts +% +% Description: For a general introduction to (sub)layouts, see +% Section~\ref{section-gd-sublayouts} in the manual. +% + +\def\pgfgdbeginlayout{ + \begingroup + \pgfgdlayoutscopeactivetrue + \advance\pgf@gd@parameter@stack@height by1\relax% + \directlua{pgf.gd.interface.InterfaceToDisplay.pushLayout(\the\pgf@gd@parameter@stack@height)} +}% + +\def\pgfgdendlayout{ + \endgroup% +}% + + + + +% Creates a subgraph node +% +% #1 = name +% #2 = node options +% #3 = node text +% +% Description: +% +% A subgraph node is a node that "surrounds" the nodes of a +% subgraph. The special property of a subgraph node opposed to a +% normal node is that it is created only after the subgraph has been +% laid out. However, the difference to a collection like "hyper" is +% that the node is available immediately as a normal node in the sense +% that you can connect edges to it. +% +% What happens internally is that subgraph nodes get "registered" +% immediately both on the pgf level and on the lua level, but the +% actual node is only created inside the layout pipeline using a +% callback. The actual node creation happens when the innermost layout +% in which the subgraph node is declared has finished. +% +% When you create a subgraph node using this macro, you also start a +% collection (of an internal kind) that stores the subgraph. All +% following nodes in the current TeX scope will become part of this +% collection. +% +% See |InterfaceToDisplay.pushSubgraphVertex| for details. + +\def\pgfgdsubgraphnode#1#2#3{% + \advance\pgf@gd@parameter@stack@height by1\relax% + {% + % create edge in Lua + \toks0={#2}% + \toks1={#3}% + \directlua{pgf.gd.interface.InterfaceToDisplay.pushSubgraphVertex% + ('\pgfutil@luaescapestring{#1}',\the\pgf@gd@parameter@stack@height, + { + shape = 'rectangle', % typically overwritten by the pgf_options: + pgf_options = '\pgfutil@luaescapestring{\the\toks0}', + text = '\pgfutil@luaescapestring{\the\toks1}', + }) + }% + } +}% + +\def\pgfgdsubgraphnodecontents#1{% helper function + \hbox to \pgfkeysvalueof{/graph drawing/subgraph bounding box width}{% + \vrule width0pt height\pgfkeysvalueof{/graph drawing/subgraph bounding box height}\hfil}% +}% + +\pgfgdset{ + subgraph point cloud/.initial=, + subgraph bounding box width/.initial=, + subgraph bounding box height/.initial=, +}% + + +% +% Requests +% +% Description: +% +% This key is used to ``request'' a graph drawing scope and a +% layout. The objective of this key is to make it easier for users and +% algorithm designers to control the slightly involved +% back-and-forth-calling between the different layers. +% +% This key does the following: When called inside a pgfpicture +% (otherwise, the call is "illegal"), it will call a call-back with +% two parameters. This callback will get passed some code that should +% be executed at the beginning of ``the next scope'' and some code +% that should be executed at the end of that scope. +% +% The code passed to the callbacks will have a different effect, +% depending on whether we are currently inside a layout scope or not +% (if no graph drawing scope is open, we are +% not inside a layout). If we are not inside a layout scope (or if the +% layout scope has been interrupted), the code will issue a +% \pgfgdbeginscope command in the opening code and a corresponding +% scope ending command in the closing code. Next, the two code pieces +% always contain \pgfgdbeginlayout and \pgfgdendlayout. +% +% Note that the "@request scope and layout" key will not immediately +% trigger a layout scope to be created; rather, the TikZ callback will +% call it only at the beginning of the scope, which will be after +% other options in the current list of options have been parsed. So, +% when you write \graph [binary tree layout, significant sep=2em] ..., +% the "significant sep" option has an effect despite being given +% *after* the "@request scope and layout" key since the actual opening +% of the scope happens only before the "..." part is parsed. + +\pgfgdset{@request scope and layout/.code=\pgfgd@requestcallback{\pgfgdbeginrequest}{\pgfgdendrequest}}% + +\def\pgfgd@requestcallback#1#2{% + #1\def\pgf@gd@after@request{#2\egroup}\bgroup\aftergroup\pgf@gd@after@request% +}% Default for basic layer + +\def\pgfgdbeginrequest{% + \ifpgfgdlayoutscopeactive% + \else% + \expandafter\pgfgdbeginscope% + \fi% + \pgfgdbeginlayout% +}% +\def\pgfgdendrequest{% + \pgfgdendlayout% + \ifpgfgdlayoutscopeactive% + \else% + \expandafter\pgfgdendscope% + \fi% +}% +\newif\ifpgfgdlayoutscopeactive + + +% Set the request callback +% +% #1 = A macro +% +% Description: +% +% Sets the request callback as described in the "@request scope and +% layout" key. + +\def\pgfgdsetrequestcallback#1{\let\pgfgd@requestcallback#1}% + + + + +% +% An if that is true exactly if we are inside a graph drawing scope +% + +\newif\ifpgfgdgraphdrawingscopeactive + + +% Begins a new graph drawing scope +% +% Description: +% +% Inside a graph drawing scope, all pgf nodes that are newly created +% are automatically passed to the graph drawing engine. In contrast, +% edges have to be indicated explicitly using the macro \pgfgdedge +% (this is because it is somewhat unclear what, exactly, should count +% as an edge). Naturally, users typically will not call \pgfgdedge +% explicitly, but have styles and keys invoke it internally. +% +% Usage: +% +% \pgfgdset{algorithm=somealgorithm} +% \pgfgdbeginscope +% \pgfnode{rectangle}{center}{A}{A}{} +% \pgfnode{rectangle}{center}{B}{B}{} +% \pgfnode{rectangle}{center}{C}{C}{} +% \pgfgdedge{A}{B}{->}{}{} +% \pgfgdedge{B}{C}{->}{}{} +% \pgfgdedge{C}{A}{->}{}{} +% \pgfgdendscope +% +% Naturally, users will typically use TikZ's somewhat simpler syntax: ' +% +% \tikz \graph [some algorithm] { A -> B -> C -> A }; + +\def\pgfgdbeginscope{% + \begingroup % Protecting scope + % get options + \directlua{ + pgf.gd.interface.InterfaceToDisplay.beginGraphDrawingScope(\the\pgf@gd@parameter@stack@height) + }% + \begingroup % Inner scope, the actual nodes will be inserted after + % this scope has been closed + % Indicate that we are now inside a graph drawing scope + \pgfgdgraphdrawingscopeactivetrue + % Switch on late positioning + \pgfpositionnodelater{\pgf@gd@positionnode@callback} + % Switch on late edges + \pgfgd@latecallback% + % Kill transformations (will be added by the position now + % macros) + \pgftransformreset +}% + + + +% The following is used to ensure that if we need to resume the graph +% drawing (through co-routines), we pass control back to TeX +% first. Otherwise, new text input levels are created and there may be +% only 15 of these... +\newif\ifpgfgdresumecoroutine + + +% Ends a graph drawing scope +% +% Description: +% +% This macro invokes the selected graph drawing algorithm and +% ships out all nodes within this scope +% +% See \pgfgdbeginscope + +\def\pgfgdendscope{% + \pgfgdresumecoroutinefalse% + \directlua{ + pgf.gd.interface.InterfaceToDisplay.runGraphDrawingAlgorithm() + }% + \pgfutil@loop% + \ifpgfgdresumecoroutine% + \pgfgdresumecoroutinefalse% + \directlua{ + pgf.gd.interface.InterfaceToDisplay.resumeGraphDrawingCoroutine() + }% + \pgfutil@repeat% + \endgroup% + % Late positioning has ended + \directlua{pgf.gd.interface.InterfaceToDisplay.renderGraph()}% + \directlua{pgf.gd.interface.InterfaceToDisplay.endGraphDrawingScope()}% + \endgroup% +}% + + + + + + +% Hook into graph specification +% +% #1 = code +% +% Description: +% +% Allows you to specify code that should be active while the graph +% drawing engine collects the information concerning the graph, but +% which should no longer be active when the graph is rendered. + +\def\pgfgdaddspecificationhook#1{ + \expandafter\def\expandafter\pgfgd@latecallback\expandafter{\pgfgd@latecallback#1} +}% +\let\pgfgd@latecallback\pgfutil@empty + + + + + + + + +% Loading further libraries + +% Include a graph drawing library file. +% +% #1 = List of names of library file. +% +% Description: +% +% This command includes a list of graph drawing library files. For +% each file X in the list, the file pgf.gd.X.lua is included using +% |require|. +% +% For the convenience of Context users, both round and square brackets +% are possible for the argument. +% +% +% Example: +% +% \usegdlibrary{trees} +% \usegdlibrary[force,circular] + +\def\usegdlibrary{\pgfutil@ifnextchar[{\use@gdlibrary}{\use@@gdlibrary}}%}% +\def\use@gdlibrary[#1]{\use@@gdlibrary{#1}}% +\def\use@@gdlibrary#1{% + \edef\pgf@list{#1}% + \pgfutil@for\pgf@temp:=\pgf@list\do{% + \expandafter\pgfkeys@spdef\expandafter\pgf@temp\expandafter{\pgf@temp}% + \ifx\pgf@temp\pgfutil@empty + \else + \directlua{if not pgf_lookup_and_require('\pgf@temp') then + tex.print('\pgfutil@luaescapestring{\noexpand\pgferror{Graph drawing library '\pgf@temp' not found}}') + end} + \fi + } +}% + +% LaTeX uses kpathsea for file lookup while ConTeXt uses its +% resolvers. Luckily, kpathsea is still accessible in ConTeXt in a +% subtable kpse.original which we use if kpse.find_file is nil. +{% +\catcode`\%=11 +\directlua{ + function pgf_lookup_and_require(name) + local sep = package.config:sub(1,1) + local function lookup(name) + local sub = name:gsub('%.',sep) + local find_file = context and + resolvers.findfile or + kpse.find_file + if find_file(sub, 'lua') then + require(name) + elseif find_file(sub, 'clua') then + collectgarbage('stop') + require(name) + collectgarbage('restart') + else + return false + end + return true + end + return + lookup('pgf.gd.' .. name .. '.library') or + lookup('pgf.gd.' .. name) or + lookup(name .. '.library') or + lookup(name) + end +} +}% + + +% +% Ok, fire up the system by creating the binding! +% +\directlua{ + require 'pgf.gd.interface.InterfaceToDisplay' + pgf.gd.interface.InterfaceToDisplay.bind(require 'pgf.gd.bindings.BindingToPGF') +}% + + + +% +% Special setup for keys that are declared by the above libraries, but +% that have a special meaning on the display layer. +% + +\pgfkeys{/graph drawing/nodes behind edges/.append code=\csname pgf@gd@nodes@behind@edges#1\endcsname}% +\pgfkeys{/graph drawing/nodes behind edges/.default=true}% + + + + +\endinput diff --git a/graphics/pgf/base/tex/generic/graphdrawing/tex/pgflibrarygraphdrawing.examples.code.tex b/graphics/pgf/base/tex/generic/graphdrawing/tex/pgflibrarygraphdrawing.examples.code.tex new file mode 100644 index 0000000000..3a1308320c --- /dev/null +++ b/graphics/pgf/base/tex/generic/graphdrawing/tex/pgflibrarygraphdrawing.examples.code.tex @@ -0,0 +1,17 @@ +% Copyright 2019 by Till Tantau +% +% This file may be distributed and/or modified +% +% 1. under the LaTeX Project Public License and/or +% 2. under the GNU Public License. +% +% See the file doc/generic/pgf/licenses/LICENSE for more details. + +\ProvidesFileRCS{pgflibrarygraphdrawing.examples.code.tex} + +\usepgflibrary{graphdrawing}% + +\usegdlibrary{examples}% + + +\endinput diff --git a/graphics/pgf/base/tex/generic/graphdrawing/tex/pgflibrarygraphdrawing.force.code.tex b/graphics/pgf/base/tex/generic/graphdrawing/tex/pgflibrarygraphdrawing.force.code.tex new file mode 100644 index 0000000000..a06d99c5d5 --- /dev/null +++ b/graphics/pgf/base/tex/generic/graphdrawing/tex/pgflibrarygraphdrawing.force.code.tex @@ -0,0 +1,136 @@ +% Copyright 2019 by Jannis Pohlmann +% +% This file may be distributed and/or modified +% +% 1. under the LaTeX Project Public License and/or +% 2. under the GNU Public License. +% +% See the file doc/generic/pgf/licenses/LICENSE for more details. + +\ProvidesFileRCS{pgflibrarygraphdrawing.force.code.tex} + + + +\usepgflibrary{graphdrawing}% + +\usegdlibrary{force}% + +\endinput + + + +% +% Definition of spring algorithms as well as options to configure them. +% + +\pgfgdset{ + force based/.cd, + % + iterations/.graph parameter=number, + iterations/.parameter initial=500, + % + cooling factor/.graph parameter=number, + cooling factor/.parameter initial=0.95, + % + initial step dimension/.graph parameter=number, + initial step dimension/.parameter initial=0, + % + convergence tolerance/.graph parameter=number, + convergence tolerance/.parameter initial=0.01, + % + spring constant/.graph parameter=number, + spring constant/.parameter initial=0.01, + % + approximate electric forces/.graph parameter=boolean, + approximate electric forces/.parameter initial=false, + approximate electric forces/.default=true, + % + electric force order/.graph parameter=number, + electric force order/.parameter initial=1, + % + % Parameters related to the multilevel approach where the input graph + % is iteratively coarsened into graphs with fewer nodes, until either + % the number of nodes is small enough or the number of nodes in the + % could not be reduced by at least a user-defined ratio. + % + coarsen/.graph parameter=boolean, + coarsen/.parameter initial=true, + coarsen/.default=true, + % + coarsening/.code=\pgfkeys{ + /graph drawing/force based/coarsening/.cd, + #1 + }, + % + coarsening/downsize ratio/.graph parameter=number, + coarsening/downsize ratio/.parameter initial=0.25, + % + coarsening/minimum graph size/.graph parameter=number, + coarsening/minimum graph size/.parameter initial=2, + % + coarsening/collapse independent edges/.graph parameter=boolean, + coarsening/collapse independent edges/.parameter initial=true, + coarsening/collapse independent edges/.default=true, + % + coarsening/connect independent nodes/.graph parameter=boolean, + coarsening/connect independent nodes/.parameter initial=false, + coarsening/connect independent nodes/.default=true, + % + % Node parameters for spring algorithms. + % + % + % Edge parameters for spring algorithms. + % +}% + +\pgfgddeclareforwardedkeys{/graph drawing}{ + % + electric charge/.node parameter=number, + electric charge/.parameter initial=1, +} + + +% +% Spring algorithm based on the paper +% +% "Efficient, High-Quality Force-Directed Graph Drawing" +% Yifan Hu, 2006 +% +\pgfgddeclarealgorithmkey + {spring layout} + {force based} + { + algorithm=pgf.gd.force.SpringHu2006 + } + + +% +% Spring-electrical algorithm based on the paper +% +% "Efficient, High-Quality Force-Directed Graph Drawing" +% Yifan Hu, 2006 +% +\pgfgddeclarealgorithmkey + {spring electrical layout} + {force based} + { + algorithm=pgf.gd.force.SpringElectricalHu2006, + force based/spring constant=0.2, + } + + + +% +% Spring-electrical algorithm based on the paper +% +% "A Multilevel Algorithm for Force-Directed Graph Drawing" +% C. Walshaw, 2000 +% +\pgfgddeclarealgorithmkey + {spring electrical layout'} + {force based} + { + algorithm=pgf.gd.force.SpringElectricalWalshaw2000, + force based/spring constant=0.01, + force based/convergence tolerance=0.001, + } diff --git a/graphics/pgf/base/tex/generic/graphdrawing/tex/pgflibrarygraphdrawing.layered.code.tex b/graphics/pgf/base/tex/generic/graphdrawing/tex/pgflibrarygraphdrawing.layered.code.tex new file mode 100644 index 0000000000..322296b11b --- /dev/null +++ b/graphics/pgf/base/tex/generic/graphdrawing/tex/pgflibrarygraphdrawing.layered.code.tex @@ -0,0 +1,17 @@ +% Copyright 2019 by Jannis Pohlmann +% +% This file may be distributed and/or modified +% +% 1. under the LaTeX Project Public License and/or +% 2. under the GNU Public License. +% +% See the file doc/generic/pgf/licenses/LICENSE for more details. + +\ProvidesFileRCS{pgflibrarygraphdrawing.layered.code.tex} + +\usepgflibrary{graphdrawing}% + +\usegdlibrary{layered}% + + +\endinput diff --git a/graphics/pgf/base/tex/generic/graphdrawing/tex/pgflibrarygraphdrawing.trees.code.tex b/graphics/pgf/base/tex/generic/graphdrawing/tex/pgflibrarygraphdrawing.trees.code.tex new file mode 100644 index 0000000000..2c05576a86 --- /dev/null +++ b/graphics/pgf/base/tex/generic/graphdrawing/tex/pgflibrarygraphdrawing.trees.code.tex @@ -0,0 +1,19 @@ +% Copyright 2019 by Renée Ahrens, Olof Frahm, Jens Kluttig, Matthias Schulz, Stephan Schuster +% Copyright 2019 by Till Tantau +% +% This file may be distributed and/or modified +% +% 1. under the LaTeX Project Public License and/or +% 2. under the GNU Public License. +% +% See the file doc/generic/pgf/licenses/LICENSE for more details. + +\ProvidesFileRCS{pgflibrarygraphdrawing.trees.code.tex} + + +\usepgflibrary{graphdrawing}% +\usepgflibrary{graphdrawing.layered}% + +\usegdlibrary{trees}% + +\endinput diff --git a/graphics/pgf/base/tex/generic/graphdrawing/tex/tikzlibrarygraphdrawing.code.tex b/graphics/pgf/base/tex/generic/graphdrawing/tex/tikzlibrarygraphdrawing.code.tex new file mode 100644 index 0000000000..aac7311e34 --- /dev/null +++ b/graphics/pgf/base/tex/generic/graphdrawing/tex/tikzlibrarygraphdrawing.code.tex @@ -0,0 +1,262 @@ +% Copyright 2018 by Renée Ahrens, Olof Frahm, Jens Kluttig, Matthias Schulz, Stephan Schuster +% Copyright 2018 by Till Tantau +% +% This file may be distributed and/or modified +% +% 1. under the LaTeX Project Public License and/or +% 2. under the GNU Public License. +% +% See the file doc/generic/pgf/licenses/LICENSE for more details. + +\ProvidesFileRCS{tikzlibrarygraphdrawing.code.tex} + +\usepgflibrary{graphdrawing}% + +\def\tikz@lib@ensure@gd@loaded{}% + + +% Patch the level and sibling distances so that gd and plain tikz are +% in sync +\tikzset{level distance=1cm, sibling distance=1cm}% + +% Patch node distance because of its special syntax. + +\pgfkeysgetvalue{/graph drawing/node distance/.@cmd}\tikz@lib@gd@node@dist@path +\pgfkeyslet{/graph drawing/node distance/orig/.@cmd}\tikz@lib@gd@node@dist@path + +\pgfkeysdef{/graph drawing/node distance}{ + \pgfutil@in@{ and }{#1}% + \ifpgfutil@in@% + \tikz@gd@convert@and#1\pgf@stop% + \else% + \tikz@gd@convert@and#1 and #1\pgf@stop% + \fi% +}% +\def\tikz@gd@convert@and#1 and #2\pgf@stop{\pgfkeys{/graph drawing/node distance/orig={#1}}}% + + + + +% +% Setup graph drawing for tikz +% + +\def\tikz@gd@request@callback#1#2{% + \tikzset{ + execute at begin scope={ + \tikzset{ + --/.style={arrows=-}, + -!-/.style={draw=none,fill=none}, + } + \pgfgdsetedgecallback{\pgfgdtikzedgecallback}% + % + % Setup for plain syntax + % + \pgfgdaddspecificationhook{\tikz@lib@gd@spec@hook}% + #1 + \let\tikzgdeventcallback\pgfgdevent% + \let\tikzgdeventgroupcallback\pgfgdeventgroup% + \let\tikzgdlatenodeoptionacallback\pgfgdsetlatenodeoption% + }, + execute at end scope={% + #2% + } + }% +}% + +\pgfgdsetrequestcallback\tikz@gd@request@callback + + + +\pgfgdappendtoforwardinglist{/tikz/,/tikz/graphs/}% + +\def\tikz@lib@gd@spec@hook{% + \tikzset{ + edge macro=\tikz@gd@plain@edge@macro, + /tikz/at/.style={/graph drawing/desired at={##1}}, + % + % Setup for the tree syntax (do this late so that grow options + % are still valid after a layout has been chosen) + % + /tikz/growth function=\relax, + /tikz/edge from parent macro=\tikz@gd@edge@from@parent@macro, + % + % Setup for the graphs syntax + % + /tikz/graphs/new ->/.code n args={4}{\tikz@lib@gd@edge{##1}{##2}{->}{/tikz,##3}{##4}}, + /tikz/graphs/new <-/.code n args={4}{\tikz@lib@gd@edge{##1}{##2}{<-}{/tikz,##3}{##4}}, + /tikz/graphs/new --/.code n args={4}{\tikz@lib@gd@edge{##1}{##2}{--}{/tikz,##3}{##4}}, + /tikz/graphs/new <->/.code n args={4}{\tikz@lib@gd@edge{##1}{##2}{<->}{/tikz,##3}{##4}}, + /tikz/graphs/new -!-/.code n args={4}{\tikz@lib@gd@edge{##1}{##2}{-!-}{/tikz,##3}{##4}}, + /tikz/graphs/placement/compute position/.code=,% + } +}% + +% wrapper for \pgfgdedge +\def\tikz@lib@gd@edge#1#2{% + \pgfgdedge{\tikz@pp@name{#1}}{\tikz@pp@name{#2}}% +} + +\pgfgdaddprepareedgehook{ + \tikz@enable@edge@quotes% + \let\tikz@transform=\pgfutil@empty% + \let\tikz@options=\pgfutil@empty% + \let\tikz@tonodes=\pgfutil@empty% +}% + + +\tikzset{ + parent anchor/.forward to=/graph drawing/tail anchor, + child anchor/.forward to=/graph drawing/head anchor +}% + +\def\pgfgdtikzedgecallback#1#2#3#4#5#6#7#8{% + \def\pgf@temp{#8}% + \ifx\pgf@temp\pgfutil@empty% + \else% + \pgfscope% + \pgfset{every graphdrawing animation/.try}% + \pgfset{every graphdrawing edge animation/.try}% + #8% + \pgfuseid{pgf@gd}% + \pgfidscope% + \fi% + \begingroup + \draw (#1) + edge [to path={#7 \tikztonodes},#3,#4,/graph drawing/.cd,#6,/tikz/.cd] + #5 + (#2); + \endgroup + \ifx\pgf@temp\pgfutil@empty% + \else% + \endpgfidscope% + \endpgfscope% + \fi% +}% + +\def\tikz@gd@edge@from@parent@macro#1#2{ + [/utils/exec=\tikz@lib@gd@edge{\tikzparentnode}{\tikzchildnode}{--}{/tikz,#1}{#2}] +}% + +\def\tikz@gd@plain@edge@macro#1#2{ + \tikz@lib@gd@edge{\tikztostart}{\tikztotarget}{--}{/tikz,#1}{#2} +}% + + + +% +% Conversions: Convert coordinates into pairs of values surrounded by +% braces. +% + +\pgfgdset{ + conversions/canvas coordinate/.code={% + \tikz@scan@one@point\pgf@process#1% + \pgfmathsetmacro{\tikz@gd@temp@x}{\pgf@x} + \pgfmathsetmacro{\tikz@gd@temp@y}{\pgf@y} + \edef\pgfgdresult{pgf.gd.model.Coordinate.new(\tikz@gd@temp@x,\tikz@gd@temp@y)} + }, + conversions/coordinate/.code={% + \tikz@scan@one@point\pgf@process#1% + \pgfmathsetmacro{\tikz@gd@temp@x}{\pgf@x} + \pgfmathsetmacro{\tikz@gd@temp@y}{\pgf@y} + \edef\pgfgdresult{pgf.gd.model.Coordinate.new(\tikz@gd@temp@x,\tikz@gd@temp@y)} + } +}% + + + +% +% Overwrite node callback +% + +\def\pgfgdcallbackcreatevertex#1#2#3#4{% + \node[every generated node/.try,name={#1},shape={#2},/graph drawing/.cd,#3]{#4};% +}% + + +% +% Subgraph handling +% + +% "General" text placement for subgraph nodes that works with all node +% kinds. + +\tikzset{ + subgraph text top/.code=\tikz@lg@simple@contents@top{#1},% + subgraph text top/.default=text ragged, + subgraph text bottom/.code=\tikz@lg@simple@contents@bottom{#1},% + subgraph text bottom/.default=text ragged, + subgraph text sep/.initial=.1em, + subgraph text none/.code={ + \def\pgfgdsubgraphnodecontents##1{% + \pgf@y=\pgfkeysvalueof{/graph drawing/subgraph bounding box height}\relax% + \hbox to \pgfkeysvalueof{/graph drawing/subgraph bounding box width}{% + \vrule width0pt height.5\pgf@y depth.5\pgf@y\hfil}% + }% + }, +}% + +\def\tikz@lg@simple@contents@top#1{% + \def\pgfgdsubgraphnodecontents##1{% + \vbox{% + \def\pgf@temp{##1}% + \ifx\pgf@temp\pgfutil@empty% + \else% + \ifx\pgf@temp\pgfutil@sptoken% + \else% + \hbox to \pgfkeysvalueof{/graph drawing/subgraph bounding box width}{% + \hsize=\pgfkeysvalueof{/graph drawing/subgraph bounding box width}\relax% + \vbox{\noindent\strut\tikzset{#1}\tikz@text@action\pgf@temp}% + }% + \fi% + \fi% + \pgfmathparse{\pgfkeysvalueof{/tikz/subgraph text sep}}% + \kern\pgfmathresult pt\relax% + \pgf@y=\pgfkeysvalueof{/graph drawing/subgraph bounding box height}\relax% + \hbox to \pgfkeysvalueof{/graph drawing/subgraph bounding box width}{% + \vrule width0pt height.5\pgf@y depth.5\pgf@y\hfil}% + }% + }% +}% + +\def\tikz@lg@simple@contents@bottom#1{% + \def\pgfgdsubgraphnodecontents##1{% + {% + \pgf@y=\pgfkeysvalueof{/graph drawing/subgraph bounding box height}\relax% + \setbox0=\vbox{% + \hbox to \pgfkeysvalueof{/graph drawing/subgraph bounding box width}{\vrule width0pt height\pgf@y\hfil}% + \pgfmathparse{\pgfkeysvalueof{/tikz/subgraph text sep}}% + \kern\pgfmathresult pt\relax% + \def\pgf@temp{##1}% + \ifx\pgf@temp\pgfutil@empty% + \else% + \ifx\pgf@temp\pgfutil@sptoken% + \else% + \hbox to \pgfkeysvalueof{/graph drawing/subgraph bounding box width}{% + \hsize=\pgfkeysvalueof{/graph drawing/subgraph bounding box width}\relax% + \vbox{\noindent\strut\tikzset{#1}\tikz@text@action\pgf@temp}% + }% + \fi% + \fi% + }% + \pgf@ya=\ht0\relax% + \advance\pgf@ya by-.5\pgf@y\relax% + \ht0=.5\pgf@y\relax% + \dp0=\pgf@ya\relax% + \box0\relax% + }% + }% +}% + +\tikzset{subgraph text top}% + +\tikzset{ + subgraph nodes/.style={/tikz/every subgraph node/.style={#1}}, + graphs/subgraph nodes/.style={/tikz/every subgraph node/.style={#1}}, + graphs/@graph drawing setup/.style={/graph drawing/anchor at={(\tikz@lastx,\tikz@lasty)}} +}% + + + +\endinput |