Layouts

EcologicalNetworksPlots.ForceDirectedLayoutType
ForceDirectedLayout

The fields are, in order:

  • move, a tuple to specify whether moves on the x and y axes are allowed
  • k, a tuple (kₐ,kᵣ) giving the strength of attraction and repulsion
  • exponents, a tuple (a,b,c,d) giving the exponents for the attraction and repulsion functions
  • gravity, the strength of attraction towards the center, set to 0.0 as a default
  • δ, a floating point constant regulating the attractive force of interaction strength – when set to its default value of 0.0, all edges have the same attraction
  • degree, a boolean to specificy whether the nodes repel one another according to their degree

The various coefficients are used to decide how strongly nodes will attract or repel one another, as a function of their distance Δ. Specifically, the default is that connected nodes will attract one another proportionally to (Δᵃ)×(kₐᵇ), with a=2 and b=-1, and all nodes repel one another proportionally to (Δᶜ)×(kᵣᵈ) with c=-1 and d=2.

The parameterization for the Fruchterman-Rheingold layout is the default one, particularly if kₐ=kᵣ. The Force Atlas 2 parameters are kₐ=1 (or b=0), kᵣ set to any value, a=1, c=-1, d=1. Note that in all cases, the gravity is a multiplying constant of the resulting attraction force, so it will also be sensitive to these choices. The FruchtermanRheingold and ForceAtlas2 functions will return a ForceDirectedLayout – as this object is mutable, you can replace the exponents at any time.

The δ parameter is particularly important for probabilistic networks, as these tend to have all their interactions set to non-zero values. As such, setting a value of δ=1 means that the interactions only attract as much as they are probable.

source

A basic example

In this example, we have a quantitative bipartite network, and we will set no gravity (nodes can move as far away as they want from the center). Note that our initial layout is a RandomInitialLayout, but we can use any layout we see fit when starting.

N = web_of_life("M_PA_003")
I = initial(RandomInitialLayout, N)
plot(I, N, aspectratio=1)
scatter!(I, N, bipartite=true)

The next step is to actually position the nodes relative to one another. Because this network has disconnected components, and we have no gravity, we expect that they will be quite far from one another:

for step in 1:(100richness(N))
  position!(ForceDirectedLayout(0.3, 0.3; gravity=0.0), I, N)
end
plot(I, N, aspectratio=1)
scatter!(I, N, bipartite=true)

We can turn gravity on just a little bit:

I = initial(RandomInitialLayout, N)
for step in 1:(100richness(N))
  position!(ForceDirectedLayout(0.3, 0.3; gravity=0.2), I, N)
end
plot(I, N, aspectratio=1)
scatter!(I, N, bipartite=true)

We can also make links attract more strongly than nodes repel (and turn gravity on some more):

I = initial(RandomInitialLayout, N)
for step in 1:(100richness(N))
  position!(ForceDirectedLayout(0.3, 0.75; gravity=0.4), I, N)
end
plot(I, N, aspectratio=1)
scatter!(I, N, bipartite=true)

Standard layouts

The force-directed code uses a series of exponents in addition to the values, to change the conformation of the resulting diagram.

EcologicalNetworksPlots.FruchtermanRheingoldFunction
FruchtermanRheingold(k::Float64; gravity::Float64=0.75)

The default ForceDirectedLayout uses the Fruchterman-Rheingold parameters - this function is simply here to make the code more explicity, and to use a "strict" version where kᵣ=kₐ.

source
EcologicalNetworksPlots.ForceAtlas2Function
ForceAtlas2(k::Float64; gravity::Float64=0.75)

In the Force Atlas 2 layout, the attraction is proportional to the distance, and the repulsion to the inverse of the distance. Note that kₐ in this layout is set to 1, so kᵣ is the relative repulsion.

source
EcologicalNetworksPlots.SpringElectricFunction
SpringElectric(k::Float64; gravity::Float64=0.75)

In the spring electric layout, attraction is proportional to distance, and repulsion to the inverse of the distance squared.

source

The Fruchterman-Rheingold method is the default:

I = initial(RandomInitialLayout, N)
for step in 1:(100richness(N))
  position!(FruchtermanRheingold(0.3; gravity=0.2), I, N)
end
plot(I, N, aspectratio=1)
scatter!(I, N, bipartite=true)

Force Atlas 2 usually gives very good results, and is in particular really good at showing the modules and long paths in a network.

I = initial(RandomInitialLayout, N)
for step in 1:(100richness(N))
  position!(ForceAtlas2(0.8; gravity=0.2), I, N)
end
plot(I, N, aspectratio=1)
scatter!(I, N, bipartite=true)

For the sake of exhaustivity, we have included the spring electric layout. This can give good results too, and is worth investigating as a possible visualisation:

I = initial(RandomInitialLayout, N)
for step in 1:(100richness(N))
  position!(SpringElectric(1.2; gravity=0.2), I, N)
end
plot(I, N, aspectratio=1)
scatter!(I, N, bipartite=true)

Degree and edge-based forces

By default, all edges have the same attraction, and nodes repel one another according to the product of their degree. In this section, we will explore the differences these choices can have.

All nodes repel at the same force, no impact of edge weight:

I = initial(RandomInitialLayout, N)
L = SpringElectric(1.2; gravity=0.2)
L.degree = false
for step in 1:(100richness(N))
  position!(L, I, N)
end
plot(I, N, aspectratio=1)
scatter!(I, N, bipartite=true)

All nodes repel at the same force, weak impact of edge weight:

I = initial(RandomInitialLayout, N)
L = SpringElectric(1.2; gravity=0.2)
L.degree = false
L.δ = 0.1
for step in 1:(100richness(N))
  position!(L, I, N)
end
plot(I, N, aspectratio=1)
scatter!(I, N, bipartite=true)

All nodes repel at the same force, strong impact of edge weight:

I = initial(RandomInitialLayout, N)
L = SpringElectric(1.2; gravity=0.2)
L.degree = false
L.δ = 2.0
for step in 1:(100richness(N))
  position!(L, I, N)
end
plot(I, N, aspectratio=1)
scatter!(I, N, bipartite=true)

Nodes repel according to their degree, strong impact of edge weight:

I = initial(RandomInitialLayout, N)
L = SpringElectric(1.2; gravity=0.2)
L.degree = true
L.δ = 2.0
for step in 1:(100richness(N))
  position!(L, I, N)
end
plot(I, N, aspectratio=1)
scatter!(I, N, bipartite=true)

Nodes repel according to their degree, some impact of edge weight:

I = initial(RandomInitialLayout, N)
L = SpringElectric(1.2; gravity=0.2)
L.degree = true
L.δ = 0.2
for step in 1:(100richness(N))
  position!(L, I, N)
end
plot(I, N, aspectratio=1)
scatter!(I, N, bipartite=true)

Food webs

One convenient way to plot food webs is to prevent them from moving on the y axis, so that every species remains at its trophic level. This can be done by changing the move field (as ForceDirectedLayout is a mutable type).

N = simplify(nz_stream_foodweb()[5])
I = initial(FoodwebInitialLayout, N)
L = SpringElectric(1.2; gravity=0.05)
L.move = (true, false)
for step in 1:(100richness(N))
  position!(L, I, N)
end
plot(I, N)
scatter!(I, N, nodefill=omnivory(N), nodesize=degree(N), c=:YlGn)