Skip to content
This repository has been archived by the owner on May 25, 2020. It is now read-only.

Commit

Permalink
Merge pull request #10 from IainNZ/ird/ipdag
Browse files Browse the repository at this point in the history
Hierachical layout
  • Loading branch information
IainNZ committed Apr 21, 2015
2 parents 16c4a92 + b1774cc commit 88c7a43
Show file tree
Hide file tree
Showing 5 changed files with 461 additions and 14 deletions.
2 changes: 1 addition & 1 deletion REQUIRE
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
julia 0.3
Docile

Requires
37 changes: 32 additions & 5 deletions example/gadfly.jl
Original file line number Diff line number Diff line change
Expand Up @@ -12,10 +12,37 @@ labels = map(chomp,readlines(fp))
close(fp)

# Calculate a layout
srand(4)
println("Layout...")
loc_x, loc_y = layout_spring_adj(adj_matrix)
#srand(4)
#println("Layout...")
#loc_x, loc_y = layout_spring_adj(adj_matrix)

# Draw it
println("Drawing...")
draw_layout_adj(adj_matrix, loc_x, loc_y, labels=labels, filename="gadfly.svg")
#rintln("Drawing...")
#draw_layout_adj(adj_matrix, loc_x, loc_y, labels=labels, filename="gadfly.svg")


adj_list = Vector{Int}[]
for i in 1:size(adj_matrix,1)
new_list = Int[]
for j in 1:size(adj_matrix,2)
if adj_matrix[i,j] != zero(eltype(adj_matrix))
push!(new_list,j)
end
end
push!(adj_list, new_list)
end

loc_x, loc_y, exp_adj_list =
GraphLayout.layout_tree(adj_list, cycles=false, ordering=:optimal)

# Correct for dummy nodes
for i in 1:(length(loc_x)-length(adj_list))
push!(labels, "")
end
exp_adj_matrix = zeros(length(loc_x),length(loc_y))
for (i,lst) in enumerate(exp_adj_list)
for j in lst
exp_adj_matrix[i,j] = 1
end
end
draw_layout_adj(exp_adj_matrix, loc_x, loc_y, labels=labels, filename="test.svg", labelsize=2.0)
20 changes: 12 additions & 8 deletions src/GraphLayout.jl
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@ module GraphLayout
if VERSION < v"0.4.0"
using Docile
end
using Requires # to optionally load JuMP
using Compose # for plotting features

# Spring-based force layout algorithms
export layout_spring_adj
Expand All @@ -11,12 +13,14 @@ module GraphLayout
export layout_stressmajorize_adj
include("stress.jl")

# Optional plotting features using Compose
export compose_layout_adj, draw_layout_adj
try
require("Compose")
include("draw.jl")
catch
global draw_layout_adj(a, x, y; kwargs...) = error("Compose.jl required for drawing functionality.")
end
# Tree layout algorithms
export layout_tree
include("tree.jl")
# Also provide optimal algorithms, that require JuMP
# JuMP will only be loaded if these methods are requested
@require JuMP include(joinpath(Pkg.dir("GraphLayout","src","tree_opt.jl")))

# Drawing utilities
export draw_layout_adj
include("draw.jl")
end
222 changes: 222 additions & 0 deletions src/tree.jl
Original file line number Diff line number Diff line change
@@ -0,0 +1,222 @@
typealias AdjList{T} Vector{Vector{T}}

@doc """
Hierachical drawing of directed graphs inspired by the Sugiyama framework.
In particular see Chapter 13 of 'Hierachical Drawing Algorithms' from
the 'Handbook of Graph Drawing and Visualization' and the article
K. Sugiyama, S. Tagawa, and M. Toda. Methods for visual understanding
of hierarchical system structures. IEEE Transaction on Systems, Man,
and Cybernetics, 11(2):109–125, 1981.
The method as implemented here has 4 steps:
1. Cycle removal [if needed]
2. Layer assignment + break up long edges
3. Vertex ordering [to reduce crossings]
4. Vertex coordinates [to straighten edges]
Arguments:
adj_list Directed graph in adjacency list format
Optional arguments:
cycles If false, assume no cycles. Default true.
ordering Vertex ordering method to use. Options are:
:optimal Uses JuMP/integer program
:barycentric Sugiyama heuristic
coord Vertex coordinate method to use. Options are:
:optimal Uses JuMP/linear program
""" ->
function layout_tree{T}(adj_list::AdjList{T};
cycles = true,
ordering = :optimal,
coord = :optimal)
n = length(adj_list)

# 1. Cycle removal
if cycles
# Need to remove cycles first
error("Cycle removal not implemented!")
end

# 2 Layering
# 2.1 Assign a layer to each vertex
layers = _layer_assmt_longestpath(adj_list)
num_layers = maximum(layers)
# 2.2 Create dummy vertices for long edges
adj_list, layers = _layer_assmt_dummy(adj_list, layers)
orig_n, n = n, length(adj_list)


# 3 Vertex ordering [to reduce crossings]
# 3.1 Build initial permutation vectors
layer_verts = [L => Int[] for L in 1:num_layers]
for i in 1:n
push!(layer_verts[layers[i]], i)
end
# 3.2 Reorder permutations to reduce crossings
if ordering == :barycentric
layer_verts = _ordering_barycentric(adj_list, layers, layer_verts)
elseif ordering == :optimal
layer_verts = _ordering_ip(adj_list, layers, layer_verts)
end


# 4. Vertex coordinates [to straighten edges]
locs_y = zeros(n)
for L in 1:num_layers
for (x,v) in enumerate(layer_verts[L])
locs_y[v] = L
end
end
locs_x = _coord_ip(adj_list, layers, layer_verts, orig_n)


return locs_x,locs_y,adj_list
end



@doc """
Assigns layers using the longest path method.
Arguments:
adj_list Directed graph in adjacency list format
""" ->
function _layer_assmt_longestpath{T}(adj_list::AdjList{T})
n = length(adj_list)
layers = fill(-1, n)

for j in 1:n
in_deg = 0
for i in 1:n
if j in adj_list[i]
in_deg += 1
end
end
if in_deg == 0
# Start recursive walk from this vertex
layers[j] = 1
_layer_assmt_longestpath_rec(adj_list, layers, j)
end
end

return layers
end
function _layer_assmt_longestpath_rec{T}(adj_list::AdjList{T}, layers, i)
# Look for all children of vertex i, try to bump their layer
n = length(adj_list)
for j in adj_list[i]
if layers[j] == -1 || layers[j] <= layers[i]
layers[j] = layers[i] + 1
_layer_assmt_longestpath_rec(adj_list, layers, j)
end
end
end


@doc """
Given a layer assignment, introduce dummy vertices to break up
long edges (more than one layer)
Arguments:
orig_adj_list Original directed graph in adjacency list format
layers Assignment of original vertices
""" ->
function _layer_assmt_dummy{T}(orig_adj_list::AdjList{T}, layers)
adj_list = deepcopy(orig_adj_list)

# This is essentially
# for i in 1:length(adj_list)
# but the adj_list is growing in the loop
i = 1
while i <= length(adj_list)
for (k,j) in enumerate(adj_list[i])
if layers[j] - layers[i] > 1
# Need to add a dummy vertex
new_v = length(adj_list) + 1
adj_list[i][k] = new_v # Replace dest of cur edge
push!(adj_list, Int[j]) # Add new edge
push!(layers, layers[i]+1) # Layer for new edge
end
end
i += 1
end

return adj_list, layers
end


@doc """
Given a layer assignment, decide a permutation for each layer
that attempts to minimizes edge crossings using the barycenter
method proposed in the Sugiyama paper.
Arguments:
adj_list Directed graph in adjacency list format
layers Assignment of vertices
layer_verts Dictionary of layer => vertices
""" ->
function _ordering_barycentric{T}(adj_list::AdjList{T}, layers, layer_verts)
num_layers = maximum(layers)
n = length(adj_list)

for iter in 1:5
# DOWN
for L in 1:num_layers-1
# Calculate barycenter for every vertex in next layer
cur_layer = layer_verts[L]
next_layer = layer_verts[L+1]
barys = zeros(n)
in_deg = zeros(n)
for (p,i) in enumerate(cur_layer)
# Because of the dummy vertices we know that
# all vertices in adj list for i are in next layer
for (q,j) in enumerate(adj_list[i])
barys[j] += p
in_deg[j] += 1
end
end
barys ./= in_deg
# Arrange next layer by barys, in ascending order
next_layer_barys = [barys[j] for j in next_layer]
#println("DOWN $L")
#println(next_layer)
#println(next_layer_barys)
layer_verts[L+1] = next_layer[sortperm(next_layer_barys)]
end
# UP
for L in num_layers-1:-1:1
# Calculate barycenters for every vertex in cur layer
cur_layer = layer_verts[L]
next_layer = layer_verts[L+1]
barys = zeros(n)
out_deg = zeros(n)
for (p,i) in enumerate(cur_layer)
# Because of the dummy vertices we know that
# all vertices in adj list for i are in next layer
# We need to know their positions in next layer
# though, unfortunately. Probably a smarter way
# to do this step
for (q,j) in enumerate(adj_list[i])
# Find position in next layer
for (r,k) in enumerate(next_layer)
if k == j
barys[i] += r
out_deg[i] += 1
break
end
end
end
end
barys ./= out_deg
# Arrange cur layer by barys, in ascending order
cur_layer_barys = [barys[j] for j in cur_layer]
#println("UP $L")
#println(cur_layer)
#println(cur_layer_barys)
layer_verts[L] = cur_layer[sortperm(cur_layer_barys)]
end
# Do something with phase 2 here - don't really understand
end

return layer_verts
end
Loading

0 comments on commit 88c7a43

Please sign in to comment.