diff --git a/README.md b/README.md index e5dd530..154af60 100644 --- a/README.md +++ b/README.md @@ -160,6 +160,10 @@ gplot(h) # Keyword Arguments + `layout` Layout algorithm: `random_layout`, `circular_layout`, `spring_layout`, `shell_layout`, `stressmajorize_layout`, `spectral_layout`. Default: `spring_layout` ++ `title` Plot title. Default: `""` ++ `title_color` Plot title color. Default: `colorant"black"` ++ `title_size` Plot title size. Default: `4.0` ++ `font_family` Font family for all text. Default: `"Helvetica"` + `NODESIZE` Max size for the nodes. Default: `3.0/sqrt(N)` + `nodesize` Relative size for the nodes, can be a Vector. Default: `1.0` + `nodelabel` Labels for the vertices, a Vector or nothing. Default: `nothing` @@ -183,6 +187,8 @@ gplot(h) + `arrowangleoffset` Angular width in radians for the arrows. Default: `π/9 (20 degrees)` + `linetype` Type of line used for edges ("straight", "curve"). Default: "straight" + `outangle` Angular width in radians for the edges (only used if `linetype = "curve`). Default: `π/5 (36 degrees)` ++ `plot_size` Tuple of measures for width x height of plot area. Default: `(sqrt(2)*10cm, 10cm)` ++ `leftpad, rightpad, toppad, bottompad` Padding for the plot margins. Default: `0mm` # Reporting Bugs diff --git a/src/GraphPlot.jl b/src/GraphPlot.jl index c1738c1..13a0a0e 100644 --- a/src/GraphPlot.jl +++ b/src/GraphPlot.jl @@ -2,6 +2,8 @@ module GraphPlot using Compose # for plotting features using Graphs +using LinearAlgebra +using SparseArrays const gadflyjs = joinpath(dirname(Base.source_path()), "gadfly.js") diff --git a/src/layout.jl b/src/layout.jl index 2a9956b..4474f90 100644 --- a/src/layout.jl +++ b/src/layout.jl @@ -102,11 +102,11 @@ julia> locs_x, locs_y = spring_layout(g) ``` """ function spring_layout(g::AbstractGraph, - locs_x=2*rand(nv(g)).-1.0, - locs_y=2*rand(nv(g)).-1.0; + locs_x::Vector{R1}=2*rand(nv(g)).-1.0, + locs_y::Vector{R2}=2*rand(nv(g)).-1.0; C=2.0, MAXITER=100, - INITTEMP=2.0) + INITTEMP=2.0) where {R1 <: Real, R2 <: Real} nvg = nv(g) adj_matrix = adjacency_matrix(g) @@ -174,7 +174,7 @@ end using Random: MersenneTwister -function spring_layout(g::AbstractGraph, seed::Integer, kws...) +function spring_layout(g::AbstractGraph, seed::Integer; kws...) rng = MersenneTwister(seed) spring_layout(g, 2 .* rand(rng, nv(g)) .- 1.0, 2 .* rand(rng,nv(g)) .- 1.0; kws...) end @@ -205,20 +205,20 @@ function shell_layout(g, nlist::Union{Nothing, Vector{Vector{Int}}} = nothing) if nv(g) == 1 return [0.0], [0.0] end - if nlist == nothing + if isnothing(nlist) nlist = [collect(1:nv(g))] end radius = 0.0 if length(nlist[1]) > 1 radius = 1.0 end - locs_x = Float64[] - locs_y = Float64[] + locs_x = zeros(nv(g)) + locs_y = zeros(nv(g)) for nodes in nlist # Discard the extra angle since it matches 0 radians. θ = range(0, stop=2pi, length=length(nodes)+1)[1:end-1] - append!(locs_x, radius*cos.(θ)) - append!(locs_y, radius*sin.(θ)) + locs_x[nodes] = radius*cos.(θ) + locs_y[nodes] = radius*sin.(θ) radius += 1.0 end return locs_x, locs_y diff --git a/src/lines.jl b/src/lines.jl index 4589641..e3d0a48 100644 --- a/src/lines.jl +++ b/src/lines.jl @@ -201,3 +201,49 @@ function curveedge(x1, y1, x2, y2, θ, outangle, d; k=0.5) return [(x1,y1) (xc1, yc1) (xc2, yc2) (x2, y2)] end + +function build_curved_edges(g, locs_x, locs_y, nodesize, arrowlengthfrac, arrowangleoffset, outangle) + if arrowlengthfrac > 0.0 + curves_cord, arrows_cord = graphcurve(g, locs_x, locs_y, nodesize, arrowlengthfrac, arrowangleoffset, outangle) + curves = curve(curves_cord[:,1], curves_cord[:,2], curves_cord[:,3], curves_cord[:,4]) + carrows = line(arrows_cord) + else + curves_cord = graphcurve(g, locs_x, locs_y, nodesize, outangle) + curves = curve(curves_cord[:,1], curves_cord[:,2], curves_cord[:,3], curves_cord[:,4]) + carrows = nothing + end + + return curves, carrows +end + +function build_straight_edges(g, locs_x, locs_y, nodesize, arrowlengthfrac, arrowangleoffset) + if arrowlengthfrac > 0.0 + lines_cord, arrows_cord = graphline(g, locs_x, locs_y, nodesize, arrowlengthfrac, arrowangleoffset) + lines = line(lines_cord) + larrows = line(arrows_cord) + else + lines_cord = graphline(g, locs_x, locs_y, nodesize) + lines = line(lines_cord) + larrows = nothing + end + + return lines, larrows +end + +function build_straight_curved_edges(g, locs_x, locs_y, nodesize, arrowlengthfrac, arrowangleoffset, outangle) + A = adjacency_matrix(g) #adjacency matrix + B = spdiagm(diag(A)) #diagonal matrix (self-loops) + A[diagind(A)] .= 0 #set diagonal elements to 0 (remove self-loops) + if is_directed(g) + g1 = SimpleDiGraph(A) + g2 = SimpleDiGraph(B) + else + g1 = SimpleGraph(A) + g2 = SimpleGraph(B) + end + + lines, larrows = build_straight_edges(g1, locs_x, locs_y, nodesize, arrowlengthfrac, arrowangleoffset) + curves, carrows = build_curved_edges(g2, locs_x, locs_y, nodesize, arrowlengthfrac, arrowangleoffset, outangle) + + return lines, larrows, curves, carrows +end \ No newline at end of file diff --git a/src/plot.jl b/src/plot.jl index af438b7..8d2b75d 100644 --- a/src/plot.jl +++ b/src/plot.jl @@ -23,6 +23,18 @@ Layout algorithm. Currently can be one of [`random_layout`, `spectral_layout`]. Default: `spring_layout` +`title` +Plot title. Default: `""` + +`title_color` +Plot title color. Default: `colorant"black"` + +`title_size` +Plot title size. Default: `4.0` + +`font_family` +Font family for all text. Default: `"Helvetica"` + `NODESIZE` Max size for the nodes. Default: `3.0/sqrt(N)` @@ -94,9 +106,18 @@ Type of line used for edges ("straight", "curve"). Default: "straight" Angular width in radians for the edges (only used if `linetype = "curve`). Default: `π/5 (36 degrees)` +`plot_size` +Tuple of measures for width x height for plot area. Default: `(sqrt(2)*10cm, 10cm)` + +`leftpad, rightpad, toppad, bottompad` +Padding for the plot margins. Default: `0mm` """ function gplot(g::AbstractGraph{T}, locs_x_in::Vector{R1}, locs_y_in::Vector{R2}; + title = "", + title_color = colorant"black", + title_size = 4.0, + font_family = "Helvetica", nodelabel = nothing, nodelabelc = colorant"black", nodelabelsize = 1.0, @@ -120,7 +141,13 @@ function gplot(g::AbstractGraph{T}, arrowlengthfrac = is_directed(g) ? 0.1 : 0.0, arrowangleoffset = π / 9, linetype = "straight", - outangle = π / 5) where {T <:Integer, R1 <: Real, R2 <: Real} + outangle = π / 5, + plot_size = (sqrt(2)*10cm, 10cm), + leftpad = 0mm, + rightpad = 0mm, + toppad = 0mm, + bottompad = 0mm + ) where {T <:Integer, R1 <: Real, R2 <: Real} length(locs_x_in) != length(locs_y_in) && error("Vectors must be same length") N = nv(g) @@ -206,33 +233,35 @@ function gplot(g::AbstractGraph{T}, end # Create lines and arrow heads - lines, arrows = nothing, nothing + lines, larrows = nothing, nothing + curves, carrows = nothing, nothing if linetype == "curve" - if arrowlengthfrac > 0.0 - curves_cord, arrows_cord = graphcurve(g, locs_x, locs_y, nodesize, arrowlengthfrac, arrowangleoffset, outangle) - lines = curve(curves_cord[:,1], curves_cord[:,2], curves_cord[:,3], curves_cord[:,4]) - arrows = line(arrows_cord) - else - curves_cord = graphcurve(g, locs_x, locs_y, nodesize, outangle) - lines = curve(curves_cord[:,1], curves_cord[:,2], curves_cord[:,3], curves_cord[:,4]) - end + curves, carrows = build_curved_edges(g, locs_x, locs_y, nodesize, arrowlengthfrac, arrowangleoffset, outangle) + elseif has_self_loops(g) + lines, larrows, curves, carrows = build_straight_curved_edges(g, locs_x, locs_y, nodesize, arrowlengthfrac, arrowangleoffset, outangle) else - if arrowlengthfrac > 0.0 - lines_cord, arrows_cord = graphline(g, locs_x, locs_y, nodesize, arrowlengthfrac, arrowangleoffset) - lines = line(lines_cord) - arrows = line(arrows_cord) - else - lines_cord = graphline(g, locs_x, locs_y, nodesize) - lines = line(lines_cord) - end + lines, larrows = build_straight_edges(g, locs_x, locs_y, nodesize, arrowlengthfrac, arrowangleoffset) end - compose(context(units=UnitBox(-1.2, -1.2, +2.4, +2.4)), - compose(context(), texts, fill(nodelabelc), stroke(nothing), fontsize(nodelabelsize)), + # Set plot_size + if length(plot_size) != 2 || !isa(plot_size[1], Compose.AbsoluteLength) || !isa(plot_size[2], Compose.AbsoluteLength) + error("`plot_size` must be a Tuple of lengths") + end + Compose.set_default_graphic_size(plot_size...) + + # Fix title offset + title_offset = isempty(title) ? 0 : 0.1*title_size/4 + + # Build figure + compose(context(units=UnitBox(-1.2, -1.2 - title_offset, +2.4, +2.4 + title_offset; leftpad, rightpad, toppad, bottompad)), + compose(context(), text(0, -1.2 - title_offset/2, title, hcenter, vcenter), fill(title_color), fontsize(title_size), font(font_family)), + compose(context(), texts, fill(nodelabelc), fontsize(nodelabelsize), font(font_family)), compose(context(), nodes, fill(nodefillc), stroke(nodestrokec), linewidth(nodestrokelw)), compose(context(), edgetexts, fill(edgelabelc), stroke(nothing), fontsize(edgelabelsize)), - compose(context(), arrows, stroke(edgestrokec), linewidth(edgelinewidth)), - compose(context(), lines, stroke(edgestrokec), fill(nothing), linewidth(edgelinewidth))) + compose(context(), larrows, stroke(edgestrokec), linewidth(edgelinewidth)), + compose(context(), carrows, stroke(edgestrokec), linewidth(edgelinewidth)), + compose(context(), lines, stroke(edgestrokec), fill(nothing), linewidth(edgelinewidth)), + compose(context(), curves, stroke(edgestrokec), fill(nothing), linewidth(edgelinewidth))) end function gplot(g; layout::Function=spring_layout, keyargs...)