Skip to content

Commit 92cf8ee

Browse files
committed
implement Align (fix JuliaGraphs#79)
1 parent dfdb678 commit 92cf8ee

File tree

3 files changed

+65
-0
lines changed

3 files changed

+65
-0
lines changed

docs/src/index.md

+17
Original file line numberDiff line numberDiff line change
@@ -104,6 +104,23 @@ nothing #hide
104104
```
105105
![spring animation](spring_animation.mp4)
106106

107+
## Aligning Layouts
108+
109+
Any two-dimensional layout can have its principal axis aligned along a desired angle (default, zero angle), by nesting an "inner" layout into an [`Align`](@ref) layout.
110+
For example, we may align the above `Spring` layout of the small cubical graph along the horizontal or vertical axes:
111+
112+
```@docs
113+
Align
114+
```
115+
```@example layouts
116+
g = smallgraph(:cubical)
117+
layout = Spring(Ptype=Float32)
118+
f, ax, p = graphplot(g, layout=layout) # horizontal alignment (zero-angle by default)
119+
hidedecorations!(ax); hidespines!(ax); ax.aspect = DataAspect(); f #hide
120+
f, ax, p = graphplot(g, layout=Align(layout, pi/2)) # vertical alignment
121+
hidedecorations!(ax); hidespines!(ax); ax.aspect = DataAspect(); f #hide
122+
```
123+
107124
## Stress Majorization
108125
```@docs
109126
Stress

src/NetworkLayout.jl

+1
Original file line numberDiff line numberDiff line change
@@ -238,5 +238,6 @@ include("stress.jl")
238238
include("spectral.jl")
239239
include("shell.jl")
240240
include("squaregrid.jl")
241+
include("align.jl")
241242

242243
end

src/align.jl

+47
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
export Align
2+
3+
"""
4+
Align(inner_layout :: AbstractLayout{2}, angle :: Real = zero(Float64))
5+
6+
Align the vertex positions of `inner_layout` so that the principal axis of the resulting
7+
layout makes an `angle` with the **x**-axis.
8+
9+
Only supports two-dimensional inner layouts.
10+
"""
11+
struct Align{Ptype, L <: AbstractLayout{2, Ptype}} <: AbstractLayout{2, Ptype}
12+
inner_layout :: L
13+
angle :: Ptype
14+
function Align(inner_layout::L, angle::Real) where {L <: AbstractLayout{2, Ptype}} where Ptype
15+
new{Ptype, L}(inner_layout, convert(Ptype, angle))
16+
end
17+
end
18+
Align(inner_layout::AbstractLayout{2, Ptype}) where Ptype = Align(inner_layout, zero(Ptype))
19+
20+
function layout(algo::Align{Ptype, <:AbstractLayout{2, Ptype}}, adj_matrix::AbstractMatrix) where {Ptype}
21+
# compute "inner" layout
22+
rs = layout(algo.inner_layout, adj_matrix)
23+
24+
# align the "inner" layout to have its principal axis make `algo.angle` with x-axis
25+
# step 1: compute covariance matrix for PCA analysis:
26+
# C = ∑ᵢ (rᵢ - ⟨r⟩) (rᵢ - ⟨r⟩)ᵀ
27+
# for vertex positions rᵢ, i = 1, …, N, and center of mass ⟨r⟩ = N⁻¹ ∑ᵢ rᵢ.
28+
centerofmass = sum(rs) / length(rs)
29+
C = zeros(SMatrix{2, 2, Ptype})
30+
for r in rs
31+
C += (r - centerofmass) * (r - centerofmass)'
32+
end
33+
vs = eigen(C).vectors
34+
35+
# step 2: pick principal axis (largest eigenvalue → last eigenvalue/vector)
36+
axis = vs[:, end]
37+
axis_angle = atan(axis[2], axis[1])
38+
39+
# step 3: rotate positions `rs` so that new axis is aligned with `algo.angle`
40+
s, c = sincos(-axis_angle + algo.angle)
41+
R = @SMatrix [c -s; s c] # [cos(θ) -sin(θ); sin(θ) cos(θ)]
42+
for (i, r) in enumerate(rs)
43+
rs[i] = Point2{Ptype}(R * r) :: Point2{Ptype}
44+
end
45+
46+
return rs
47+
end

0 commit comments

Comments
 (0)