From 5d88e5d9173192c8f5458743154b73fc262450ba Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Timoth=C3=A9e=20Poisot?= Date: Thu, 26 Nov 2020 09:27:41 -0500 Subject: [PATCH 01/13] =?UTF-8?q?=F0=9F=8E=89=20initial=20layouts=20are=20?= =?UTF-8?q?scaled=20by=20the=20square=20root=20of=20richness?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/initial_layouts.jl | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/src/initial_layouts.jl b/src/initial_layouts.jl index b74ef73..ce6cb57 100644 --- a/src/initial_layouts.jl +++ b/src/initial_layouts.jl @@ -1,11 +1,18 @@ """ initial(::Type{RandomInitialLayout}, N::T) where {T <: EcologicalNetworks.AbstractEcologicalNetwork} -Random disposition of nodes in the unit square. This is a good starting -point for any force-directed layout. +Random disposition of nodes in a circle. This is a good starting point for any +force-directed layout. The circle is scaled so that its radius is twice the +square root of the network richness, which helps most layouts converge faster. """ function initial(::Type{RandomInitialLayout}, N::T) where {T <: EcologicalNetworks.AbstractEcologicalNetwork} - return Dict([s => NodePosition() for s in species(N)]) + L = Dict([s => NodePosition() for s in species(N)]) + _adj = 2sqrt(richness(N)) + for s in species(N) + L[s].x *= _adj + L[s].y *= _adj + end + return L end """ From 8c9d7f591b0984da21a27c7f428bbb010929126b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Timoth=C3=A9e=20Poisot?= Date: Thu, 26 Nov 2020 09:30:03 -0500 Subject: [PATCH 02/13] =?UTF-8?q?=F0=9F=8E=89=20food=20web=20layout=20also?= =?UTF-8?q?=20scaled?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/initial_layouts.jl | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/src/initial_layouts.jl b/src/initial_layouts.jl index ce6cb57..c2e822f 100644 --- a/src/initial_layouts.jl +++ b/src/initial_layouts.jl @@ -33,16 +33,16 @@ end initial(::Type{FoodwebInitialLayout}, N::T) where {T <: EcologicalNetworks.AbstractUnipartiteNetwork} Random disposition of nodes on trophic levels for food webs. Note that the -*fractional* trophic level is used, but the layout can be modified afterwards -to use the continuous levels. +continuous trophic level is used, but the layout can be modified afterwards to +use another measure of trophic rank. """ function initial(::Type{FoodwebInitialLayout}, N::T) where {T <: EcologicalNetworks.AbstractUnipartiteNetwork} - level = NodePosition[] - tl = fractional_trophic_level(N) - for (i, s) in enumerate(species(N)) - push!(level, NodePosition(rand(), float(tl[s]), 0.0, 0.0)) + L = initial(RandomInitialLayout, N) + tl = trophic_level(N) + for s in species(N) + L[s].y = tl[s] end - return Dict(zip(species(N), level)) + return L end """ From a441cfdd266e01669bc86618f5c20fa39f9e84bf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Timoth=C3=A9e=20Poisot?= Date: Thu, 26 Nov 2020 09:35:04 -0500 Subject: [PATCH 03/13] =?UTF-8?q?=F0=9F=94=A7=20simplify=20the=20update!?= =?UTF-8?q?=20code?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/forcedirected.jl | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/forcedirected.jl b/src/forcedirected.jl index 85b0372..3109e85 100644 --- a/src/forcedirected.jl +++ b/src/forcedirected.jl @@ -125,9 +125,10 @@ Update the position of a node """ function update!(n::NodePosition) Δ = sqrt(n.vx^2.0+n.vy^2.0) - Δ = Δ == 0.0 ? 0.0001 : Δ - n.x += n.vx/Δ*min(Δ, 0.01) - n.y += n.vy/Δ*min(Δ, 0.01) + if !iszero(Δ) + n.x += n.vx/Δ*min(Δ, 0.01) + n.y += n.vy/Δ*min(Δ, 0.01) + end stop!(n) end From 1eb1fb1966976635370c239dd961422603213c7b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Timoth=C3=A9e=20Poisot?= Date: Thu, 26 Nov 2020 09:35:19 -0500 Subject: [PATCH 04/13] =?UTF-8?q?=F0=9F=93=96=20more=20infos=20on=20how=20?= =?UTF-8?q?long=20the=20FD=20layouts=20should=20run?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/forcedirected.jl | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/forcedirected.jl b/src/forcedirected.jl index 3109e85..c3ca591 100644 --- a/src/forcedirected.jl +++ b/src/forcedirected.jl @@ -139,6 +139,13 @@ One iteration of the force-directed layout routine. Because these algorithms can take some time to converge, it may be useful to stop every 500 iterations to have a look at the results. Note that to avoid oscillations, the maximum displacement at any given time is set to 0.01 units. + +These layouts tend to have O(N³) complexity, where N is the number of nodes in +the network. This is because repulsion required to do (N×(N-1))/2 visits on +pairs of nodes, and an optimal layout usually requires s×N steps to converge. +With the maximal displacement set to 0.01, we have found that k ≈ 100 gives +acceptable results. This will depend on the complexity of the network, and its +connectance, as well as the degree and edge strengths distributions. """ function position!(LA::ForceDirectedLayout, L::Dict{K,NodePosition}, N::T) where {T <: EcologicalNetworks.AbstractEcologicalNetwork} where {K} From 6ab516e7964ad5d23ed45773b5b54c51547b9d23 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Timoth=C3=A9e=20Poisot?= Date: Thu, 26 Nov 2020 09:35:41 -0500 Subject: [PATCH 05/13] =?UTF-8?q?=F0=9F=93=A6=20version=20bump?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Project.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Project.toml b/Project.toml index f1e6601..3e610c9 100644 --- a/Project.toml +++ b/Project.toml @@ -1,7 +1,7 @@ name = "EcologicalNetworksPlots" uuid = "9f7a259d-73a7-556d-a7a2-3eb122d3865b" authors = ["Timothée Poisot "] -version = "0.0.8" +version = "0.0.9" [deps] EcologicalNetworks = "f03a62fe-f8ab-5b77-a061-bb599b765229" From 104bb5358399518752a9884700c84f274bb84f11 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Timoth=C3=A9e=20Poisot?= Date: Thu, 26 Nov 2020 09:49:04 -0500 Subject: [PATCH 06/13] update example --- docs/src/layouts/forcedirected.md | 44 +++++++++++++------------------ 1 file changed, 18 insertions(+), 26 deletions(-) diff --git a/docs/src/layouts/forcedirected.md b/docs/src/layouts/forcedirected.md index 8cbc152..7ffe9ef 100644 --- a/docs/src/layouts/forcedirected.md +++ b/docs/src/layouts/forcedirected.md @@ -31,7 +31,7 @@ this network has disconnected components, and we have no gravity, we expect that they will be quite far from one another: ```@example default -for step in 1:5000 +for step in 1:(100richness(N)) position!(ForceDirectedLayout(0.3, 0.3; gravity=0.0), I, N) end plot(I, N, aspectratio=1) @@ -42,7 +42,7 @@ We can turn gravity on just a little bit: ```@example default I = initial(RandomInitialLayout, N) -for step in 1:5000 +for step in 1:(100richness(N)) position!(ForceDirectedLayout(0.3, 0.3; gravity=0.2), I, N) end plot(I, N, aspectratio=1) @@ -54,7 +54,7 @@ on some more): ```@example default I = initial(RandomInitialLayout, N) -for step in 1:5000 +for step in 1:(100richness(N)) position!(ForceDirectedLayout(0.3, 0.75; gravity=0.4), I, N) end plot(I, N, aspectratio=1) @@ -76,7 +76,7 @@ The Fruchterman-Rheingold method is the default: ```@example default I = initial(RandomInitialLayout, N) -for step in 1:5000 +for step in 1:(100richness(N)) position!(FruchtermanRheingold(0.3; gravity=0.2), I, N) end plot(I, N, aspectratio=1) @@ -88,7 +88,7 @@ at showing the modules and long paths in a network. ```@example default I = initial(RandomInitialLayout, N) -for step in 1:5000 +for step in 1:(100richness(N)) position!(ForceAtlas2(0.8; gravity=0.2), I, N) end plot(I, N, aspectratio=1) @@ -101,7 +101,7 @@ visualisation: ```@example default I = initial(RandomInitialLayout, N) -for step in 1:5000 +for step in 1:(100richness(N)) position!(SpringElectric(1.2; gravity=0.2), I, N) end plot(I, N, aspectratio=1) @@ -120,7 +120,7 @@ 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:5000 +for step in 1:(100richness(N)) position!(L, I, N) end plot(I, N, aspectratio=1) @@ -134,7 +134,7 @@ I = initial(RandomInitialLayout, N) L = SpringElectric(1.2; gravity=0.2) L.degree = false L.δ = 0.1 -for step in 1:5000 +for step in 1:(100richness(N)) position!(L, I, N) end plot(I, N, aspectratio=1) @@ -148,7 +148,7 @@ I = initial(RandomInitialLayout, N) L = SpringElectric(1.2; gravity=0.2) L.degree = false L.δ = 2.0 -for step in 1:5000 +for step in 1:(100richness(N)) position!(L, I, N) end plot(I, N, aspectratio=1) @@ -162,7 +162,7 @@ I = initial(RandomInitialLayout, N) L = SpringElectric(1.2; gravity=0.2) L.degree = true L.δ = 2.0 -for step in 1:5000 +for step in 1:(100richness(N)) position!(L, I, N) end plot(I, N, aspectratio=1) @@ -176,7 +176,7 @@ I = initial(RandomInitialLayout, N) L = SpringElectric(1.2; gravity=0.2) L.degree = true L.δ = 0.2 -for step in 1:5000 +for step in 1:(100richness(N)) position!(L, I, N) end plot(I, N, aspectratio=1) @@ -187,24 +187,16 @@ scatter!(I, N, bipartite=true) 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). Note -that in this example, we *update* the layout after plotting, by replacing the -fractional trophic level by the actual trophic level (and we color the nodes by -their degree, which is covered more in-depth in the next section of this -documentation). +changing the `move` field (as `ForceDirectedLayout` is a mutable type). ```@example default -Fweb = simplify(nz_stream_foodweb()[1]) -I = initial(FoodwebInitialLayout, Fweb) +N = simplify(nz_stream_foodweb()[1]) +I = initial(FoodwebInitialLayout, N) L = SpringElectric(1.2; gravity=0.05) L.move = (true, false) -for step in 1:5000 - position!(L, I, Fweb) -end -tl = trophic_level(Fweb) -for s in species(Fweb) - I[s].y = tl[s] +for step in 1:(100richness(N)) + position!(L, I, N) end -plot(I, Fweb) -scatter!(I, Fweb, nodefill=degree(Fweb), c=:YlGn) +plot(I, N) +scatter!(I, N, nodefill=omnivory(N), nodesize=degree(N), c=:YlGn) ``` \ No newline at end of file From 95d65f48ff63950d521de6fec17773c80998634f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Timoth=C3=A9e=20Poisot?= Date: Thu, 26 Nov 2020 09:53:55 -0500 Subject: [PATCH 07/13] =?UTF-8?q?=E2=9A=A1=20skip=20one=20step=20if=20dist?= =?UTF-8?q?ance=20is=20zero?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/forcedirected.jl | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/src/forcedirected.jl b/src/forcedirected.jl index c3ca591..15b5c51 100644 --- a/src/forcedirected.jl +++ b/src/forcedirected.jl @@ -109,14 +109,15 @@ function attract!(LA::T, n1::NodePosition, n2::NodePosition, fa) where {T <: For δx = n1.x - n2.x δy = n1.y - n2.y Δ = sqrt(δx^2.0+δy^2.0) - Δ = Δ == 0.0 ? 0.0001 : Δ - if LA.move[1] - n1.vx -= δx/Δ*fa(Δ) - n2.vx += δx/Δ*fa(Δ) - end - if LA.move[2] - n1.vy -= δy/Δ*fa(Δ) - n2.vy += δy/Δ*fa(Δ) + if !iszero(Δ) + if LA.move[1] + n1.vx -= δx/Δ*fa(Δ) + n2.vx += δx/Δ*fa(Δ) + end + if LA.move[2] + n1.vy -= δy/Δ*fa(Δ) + n2.vy += δy/Δ*fa(Δ) + end end end From bebd39c68ec451e6748e188c38e983913ec375d0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Timoth=C3=A9e=20Poisot?= Date: Thu, 26 Nov 2020 10:07:21 -0500 Subject: [PATCH 08/13] =?UTF-8?q?=F0=9F=93=96=20use=20a=20tall=20network?= =?UTF-8?q?=20for=20the=20doc?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/src/layouts/forcedirected.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/src/layouts/forcedirected.md b/docs/src/layouts/forcedirected.md index 7ffe9ef..7b60231 100644 --- a/docs/src/layouts/forcedirected.md +++ b/docs/src/layouts/forcedirected.md @@ -190,7 +190,7 @@ 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). ```@example default -N = simplify(nz_stream_foodweb()[1]) +N = simplify(nz_stream_foodweb()[5]) I = initial(FoodwebInitialLayout, N) L = SpringElectric(1.2; gravity=0.05) L.move = (true, false) From cd6c66a621e5ea1236bb282ac8bdb1d8eb108a32 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Timoth=C3=A9e=20Poisot?= Date: Thu, 26 Nov 2020 10:16:09 -0500 Subject: [PATCH 09/13] =?UTF-8?q?=F0=9F=94=A7=20build=20docs=20with=20diff?= =?UTF-8?q?erent=20token?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/TagBot.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/TagBot.yml b/.github/workflows/TagBot.yml index d77d3a0..a335de6 100644 --- a/.github/workflows/TagBot.yml +++ b/.github/workflows/TagBot.yml @@ -8,4 +8,4 @@ jobs: steps: - uses: JuliaRegistries/TagBot@v1 with: - token: ${{ secrets.GITHUB_TOKEN }} + token: ${{ secrets.TOKEN }} From 3cd28dd2ef817c7c630d27ad8f44d794e3fe9d9b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Timoth=C3=A9e=20Poisot?= Date: Thu, 26 Nov 2020 10:21:42 -0500 Subject: [PATCH 10/13] =?UTF-8?q?=F0=9F=93=96=20new=20modular=20example=20?= =?UTF-8?q?network?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/src/advanced/subsets.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/src/advanced/subsets.md b/docs/src/advanced/subsets.md index c2aa186..0456ebd 100644 --- a/docs/src/advanced/subsets.md +++ b/docs/src/advanced/subsets.md @@ -29,14 +29,14 @@ scatter!(I, N[core3], mc=:red) We can also use this ability to show the modular structure of a network: ```@example default -N = convert(BinaryNetwork, Umod) +N = web_of_life("A_HP_029") I = initial(RandomInitialLayout, N) for step in 1:2000 position!(SpringElectric(1.2; gravity=0.75), I, N) end # Modularity -_, P = brim(lp(N)...) +B, P = brim(lp(convert(BinaryNetwork, N))...) plot(I, N, aspectratio=1) scatter!(I, N, msw=0.0, nodefill=P, c=:Set2) ``` \ No newline at end of file From e5814ad7e5e4b7ad8c3d66eaa38a514c10bdaaa3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Timoth=C3=A9e=20Poisot?= Date: Thu, 26 Nov 2020 10:36:11 -0500 Subject: [PATCH 11/13] =?UTF-8?q?=F0=9F=93=96=20update?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/src/advanced/subsets.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/src/advanced/subsets.md b/docs/src/advanced/subsets.md index 0456ebd..bbbca71 100644 --- a/docs/src/advanced/subsets.md +++ b/docs/src/advanced/subsets.md @@ -29,7 +29,7 @@ scatter!(I, N[core3], mc=:red) We can also use this ability to show the modular structure of a network: ```@example default -N = web_of_life("A_HP_029") +N = web_of_life("M_PA_003") I = initial(RandomInitialLayout, N) for step in 1:2000 position!(SpringElectric(1.2; gravity=0.75), I, N) From 88e2290e8c6100bfa2a1ee8493ac93fea42aec9a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Timoth=C3=A9e=20Poisot?= Date: Thu, 26 Nov 2020 10:37:53 -0500 Subject: [PATCH 12/13] :red_circle: fix branch building --- .github/workflows/Documentation.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/Documentation.yml b/.github/workflows/Documentation.yml index b60c221..2eebaf2 100644 --- a/.github/workflows/Documentation.yml +++ b/.github/workflows/Documentation.yml @@ -4,7 +4,7 @@ on: release: push: branches: - - main + - master tags: - '*' pull_request: From b12869dd81e7685ab882d7f90999b13fb1772bd7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Timoth=C3=A9e=20Poisot?= Date: Thu, 26 Nov 2020 10:43:17 -0500 Subject: [PATCH 13/13] Update README.md --- README.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 110cfe7..8a8417c 100644 --- a/README.md +++ b/README.md @@ -6,8 +6,8 @@ generates force-directed layouts, circular layouts, bipartite layouts, heatmaps, as well as layouts based on node properties. Note that `Plots.jl` *must* be installed in the project, and loaded, for this package to work. -**Documentation:** [Stable](https://ecojulia.github.io/EcologicalNetworksPlots.jl/stable/) `//` [Latest](https://ecojulia.github.io/EcologicalNetworksPlots.jl/latest/) +[![latest doc](https://img.shields.io/badge/documentation-stable-brightgreen)](https://ecojulia.github.io/EcologicalNetworksPlots.jl/stable/) [![latest doc](https://img.shields.io/badge/documentation-latest-green)](https://ecojulia.github.io/EcologicalNetworksPlots.jl/latest/) -![CI](https://github.com/EcoJulia/EcologicalNetworksPlots.jl/workflows/CI/badge.svg?branch=master) +![CI](https://github.com/EcoJulia/EcologicalNetworksPlots.jl/workflows/CI/badge.svg?branch=master) [![codecov](https://codecov.io/gh/EcoJulia/EcologicalNetworksPlots.jl/branch/master/graph/badge.svg?token=HKaubLliPG)](https://codecov.io/gh/EcoJulia/EcologicalNetworksPlots.jl) -[![DOI](https://zenodo.org/badge/143920106.svg)](https://zenodo.org/badge/latestdoi/143920106) +[![Project Status: Active – The project has reached a stable, usable state and is being actively developed.](https://www.repostatus.org/badges/latest/active.svg)](https://www.repostatus.org/#active) [![DOI](https://zenodo.org/badge/143920106.svg)](https://zenodo.org/badge/latestdoi/143920106)